java面向对象
1、概述
- 什么是面向过程?
面向过程——步骤化
分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,自顶向下,逐步细化,接着依次调用。 - 什么是面向对象?
面向对象——行为化
面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,使用属性描述对象的状态,使用方法来处理对象的行为。
复杂问题简单化
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。
蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性“比较好,”饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
2、 类与对象
同一事物的统称,比如:人类,鸟类
类:构造对象的模板或蓝图。比如:制作月饼的模具可以必做类,月饼可以比作对象
定义类
声明类
修饰符 class 类名 extends 父类名 implements 接口列表
修饰符:public、abstract、final可选
public class Student{ // 定义一个学生类
String name; // 定义一个姓名属性
double score; // 定义一个分数属性
// 定义一个学习方法
public void study(){
System.out.println(name + "学习java");
}
//定义一个考试方法
public void exam(){
System.out.println( (int)(Math.random() * 100) );
}
}
定义对象
- 创建
Student stu = new Student();
- 使用
stu.name //对象.成员变量
stu.study() //对象.成员方法
局部变量、成员变量、实例变量、类变量
- 成员变量:类中声明,整个类有效
修饰符 static|final 变量类型 变量名
举个栗子:public static int count - 局部变量:方法中声明,方法中有效,必须先赋值再使用
final 变量类型 变量名
举个栗子:final int age; - 成员方法
修饰符 方法返回值的类型 方法名(参数列表){
方法体
}
修饰符:public、protected、private可选
public class Student{
public String name; // 成员变量
public int age; // 成员变量
public static void main(String[] args) { //主方法
}
public void getBirthDate{ // 成员方法
final int birthDate; // 局部变量
birthDate = 2020 - age;
return birthDate;
}
}
3、 构造方法
-
特点
(1)名字必须和类名完全相同
(2)没有返回值
(3)不需要void关键字进行标识
(4)需要用 new关键字创建对象后使用(5)每个类可以有一个以上的构造器
-
用途
对对象中的所有成员变量进行初始化
无参构造方法
public class Student{
public String name;
public int age;
public Student (){ // 构造方法
name = "xiaoxiao";
age = 20;
}
public static void main(String[] args) {
Student stu = new Student();
System.out.println(stu.age);
}
}
有参构造方法
public class Student{
public String name;
public int age;
public Student (String name,int age){ // 构造方法
this.name=name;
this.age=age;
}
public static void main(String[] args) {
Student stu = new Student("xiaoxiao",20);
System.out.println(stu.age);
}
}
注意:不要在构造方法中定义与实例域重命的局部变量
如果没有定义构造器 ,系统会自动给类生成一个无参构造器(默认构造器)
4、面向对象的三大特点
封装
概念
隐藏对象的属性和方法,提供控制对象的修改及访问的权限
封装的作用
便于使用、提高重用性、提高安全性
封装的步骤
1.使用 private
关键字来修饰成员变量。
2.使用public
修饰getter和setter方法。
- getXXX() 方法:实现取值
- setXXX() 方法:实现赋值,可对数据进行检测和过滤,保证数据安全
public class Student{
String name;
private int age;//私有属性(本类可见)
private String sex;
double score;
public void setAge(int age){ //set方法进行赋值、数据的检测和过滤
if(age > 0 && age <= 253){
this.age = age;
}else{
//Future:抛出异常
this.age = 18;
}
}
public int getAge(){ // get方法进行取值
return this.age;
}
public static void main(String[] args){
Student stu = new Student();
stu.name = "tom";
//stu.age = 20;//Error 不能直接访问私有属性
stu.setAge(20); //获得stu对象的age属性的值
System.out.println( stu.getAge() );
}
}
继承
概念
子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
语法格式
修饰符 class 类名 extends 父类名{}
需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。
作用
实现代码的重用,提高程序的可扩展性
不可继承
-
类中的构造方法,只负责创建本类对象,不可继承
-
private修饰的属性和方法
-
子类不能继承父类的构造器,因为子类有自己的构造器。
-
父子类不在同一package中时,default修饰的属性和方法。(未理解)
-
特点
(1) 继承父类被声明为public和protected的成员变量和成员方法,不能继承被声明为private成员变量和成员方法
(2) 子类能够继承同一个包中的由默认修饰符修饰的成员变量和成员方法
(3) 如果子类有一个与父类同名的成员变量,则父类的成员变量被隐藏
(4) 如果子类有一个与父类同名的成员方法,则父类的成员方法被覆盖
值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
继承的内存分配
多态
方法的重载
- 概念:一个类中出现多个同名方法
- 特点:
(1)方法名相同
(2)参数列表不同(类型,个数,顺序)
(3)与访问修饰符、返回值类型无关。
(4)java根据调用的参数个数或参数类型来区分具体执行的方法
public class Function_test {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a = add(4,9);
System.out.println(a);
int b = add(4,9,4);
System.out.println(b);
}
// add函数,获取两个值的和
public static int add(int x,int y){
int sum = x+y;
return sum;
}
// add函数的重载 ,获取三个值的和
public static int add(int x,int y,int z){
int sum = x+y+z;
return sum;
}
}
@Override重写注解
-
@Override:注解,重写注解校验!
-
这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
-
建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!
加上后的子类代码形式如下:
public class Cat extends Animal { // 声明不变,重新实现 // 方法名称与父类全部一样,只是方法体中的功能重写写了! @Override public void cry(){ System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!"); } }
方法的覆盖
定义:子类补充或改变父类方法
public class Test{
public static void main(String[] args){
Son son = new Son();
son.workVehicle();
}
}
class Father{
public static void workVehicle(){
System.out.println("骑自行车去上班");
}
}
class Son extends Father{
public static void workVehicle(){
System.out.println("开车去上班");
}
}
5、各种关键字
abstract 关键字
-
抽象类
-
抽象方法
(1)abstract修饰类:不能new对象,但可以声明引用。
(2)abstract修饰方法:只有方法声明,没有方法实现。(需包含在抽象类中)
(3)抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类。
(4)子类继承抽象类后,必须覆盖父类所有的抽象方法,否则子类还是抽象类。
final关键字
final修饰类 :此类不能被继承
final修饰方法 : 此方法不能被覆盖
final 修饰变量 : 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
super关键字
-
用途
子类中的成员变量名或成员方法名与父类的相同时,如果想在子类中访问父类中被隐藏的成员变量或成员方法时,用super关键字 -
用法
(1) 在子类的构造方法的首行,使用“super()”或“super(实参)”,调用父类的构造方法
(2)访问父类的属性,但不能访问父类的private属性(3)访问父类的方法,但不能访问父类的private方法
(4)当子类和父类有相同的属性和方法时,访问父类的属性和方法必须通过super,当子类的父类的属性和方法不同时,带不带super效果一样,最好还是带着。
super.成员变量名
super.成员方法名(参数列表)
this关键字
- 特点
(1) 可以出现在实例方法和构造方法中,不可再类方法中出现
(2) 当局部变量和成员变量同名时,成员变量就会被隐藏,在成员方法中使用成员变量,使用this关键字。
class Person
{
private String name;
private int age;
Person(String name){
this.name = name;//成员变量和局部变量重名,可以用关键字this区分
}
public void speak(){
System.out.println(this.name+":"+this.age); //输出时,会默认为成员增加this关键字,用来代表具体对象的数据
}
}
- 引用情形
调用实例属性和实例方法
调用本类中的其他构造方法
使用 this 和 super 要注意的问题:
- 在构造器中使用
super()
调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 - this、super不能用在static方法中。
super和this的比较
解释一下:
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。
instanceof关键字
Java中的 instanceof 关键字 **可以判断一个对象到底是哪个类的实例化 ** 返回 boolean 的数据类型
- instanceof作用的类型
int a = 1;
System.out.println(a instanceof Integer); //编译不通过
System.out.println(a instanceof Object); //编译不通过
instanceof运算符只能对引用类型进行判断,不能是基本类型。
- instanceof作用于null
System.out.println(null instanceof Object);//false
如果 obj 为 null,那么将返回 false
可以用instanceof关键字判断对象类型的同时判断对象是否为空,不用另外做判断
- instanceof作用于实例对象
Integer a = new Integer(1);
System.out.println(a instanceof Integer); // ture
- instanceof作用于实现类
了解Java集合类的小伙伴都知道,List作为上层接口,有不少经典的实现类如ArrayList、LinkedList等。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
可以使用instanceof关键字来判断,某个对象是否是List接口的实现类
LinkedList linkedList = new LinkedList();
System.out.println(linkedList instanceof LinkedList); //true
反过来也是返回true
List list = new LinkedList<>();
System.out.println(list instanceof LinkedList);
- instanceof作用于直接或间接子类
public class test {
static class Person {
}
static class Father extends Person {
}
static class Son extends Father {
}
public static void main(String[] args) {
Person person = new Person();
Father father = new Father();
Person p = new Father();
Son son = new Son();
System.out.println(person instanceof Father); // false
System.out.println(father instanceof Person); // true
System.out.println(son instanceof Father); // true
System.out.println(son instanceof Person); // true
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Father); // true
}
}
- 注意
在判断某个类(或接口)的对象是不是其他类(或接口)的实例,一定要先进行向上转型,然后才可以用instanceof关键字进行判断。
interface Animal{} //动物接口
class Feline implements Animal{} //猫科动物类
class Canine implements Animal{} //犬科动物类
public class TestInstanceof{
public static void main(String[] args) {
Animal a = new Feline();
System.out.println(a instanceof Feline); //true
System.out.println(a instanceof Canine); //false
}
}
在判断接口Animal的对象a是不是类Canine的实例时,因为没有先进行向上转型,所以instanceof关键字判断的时候返回为false。
static关键字
- 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:
类名.静态变量名
类名.静态方法名()
- 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
- 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:
import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
public class Student{
public String name; // 成员变量
public int age; // 成员变量
public static void main(String[] args) { //主方法
}
public int getAge{ // 实例方法
return age;
}
static void getName{ // 类方法
return name;
}
}
public class TestStatic{
public static void main(String[] args){
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
mc1.a = 10;
mc2.a = 20;
mc1.b = 100; //自动转换 mc1.b ---> MyClass.b
mc2.b = 200;
System.out.println(mc1.a +"\t"+ mc2.a);
System.out.println(MyClass.b +"\t"+ MyClass.b); //静态成员通过类名访问
}
}
class MyClass{
int a;
static int b;
}
修饰成员变量和成员方法(常用)
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
调用格式:
类名.静态变量名
类名.静态方法名()
如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。
测试方法:
public class StaticBean {
String name;
//静态变量
static int age;
public StaticBean(String name) {
this.name = name;
}
//静态方法
static void SayHello() {
System.out.println("Hello i am java");
}
@Override
public String toString() {
return "StaticBean{"+
"name=" + name + ",age=" + age +
"}";
}
}
public class StaticDemo {
public static void main(String[] args) {
StaticBean staticBean = new StaticBean("1");
StaticBean staticBean2 = new StaticBean("2");
StaticBean staticBean3 = new StaticBean("3");
StaticBean staticBean4 = new StaticBean("4");
StaticBean.age = 33;
System.out.println(staticBean + " " + staticBean2 + " " + staticBean3 + " " + staticBean4);
//StaticBean{name=1,age=33} StaticBean{name=2,age=33} StaticBean{name=3,age=33} StaticBean{name=4,age=33}
StaticBean.SayHello();//Hello i am java
}
}
静态代码块
静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
静态代码块的格式是
static {
语句体;
}
一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问.
静态内部类
静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
- 它的创建是不需要依赖外围类的创建。
- 它不能使用任何外围类的非static成员变量和方法。
Example(静态内部类实现单例模式)
public class Singleton {
//声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
静态导包
格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
//将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
//如果只想导入单一某个静态方法,只需要将换成对应的方法名即可
import static java.lang.Math.*;//换成import static java.lang.Math.max;具有一样的效果
public class Demo {
public static void main(String[] args) {
int max = max(1,2);
System.out.println(max);
}
}
补充内容
静态方法与非静态方法
静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。
Example
class Foo {
int i;
public Foo(int i) {
this.i = i;
}
public static String method1() {
return "An example string that doesn't depend on i (an instance variable)";
}
public int method2() {
return this.i + 1; //Depends on i
}
}
你可以像这样调用静态方法:Foo.method1()
。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:Foo bar = new Foo(1);bar.method2();
总结:
- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
static{}
静态代码块与{}
非静态代码块(构造代码块)
相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
修正 issue #677:静态代码块可能在第一次new的时候执行,但不一定只在第一次new的时候执行。比如通过
Class.forName("ClassDemo")
创建 Class 对象的时候也会执行。
一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
Example:
public class Test {
public Test() {
System.out.print("默认构造方法!--");
}
//非静态代码块
{
System.out.print("非静态代码块!--");
}
//静态代码块
static {
System.out.print("静态代码块!--");
}
private static void test() {
System.out.print("静态方法中的内容! --");
{
System.out.print("静态方法中的代码块!--");
}
}
public static void main(String[] args) {
Test test = new Test();
Test.test();//静态代码块!--静态方法中的内容! --静态方法中的代码块!--
}
}
上述代码输出:
静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!--
当只执行 Test.test();
时输出:
静态代码块!--静态方法中的内容! --静态方法中的代码块!--
当只执行 Test test = new Test();
时输出:
静态代码块!--非静态代码块!--默认构造方法!--
非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
6、访问权限
- 私有变量和私有方法
- 公有变量和公有方法
- 友好变量和友好方法
7、内部类
-
概念:在一个类或者一个方法的内部再定义一个完整的类。
-
特点
(1)编译之后可生成独立的字节码文件。(2)内部类可直接访问外部类的私有成员,而不破坏封装。
(3) 可为外部类提供必要的内部功能组件。
成员内部类
- 特点:
(1)当外部类和内部类重命时,优先访问内部类
(2)成员内部类不能定义静态成员
public class MemberInnerClass{
public static void main(String[] args) {
Outer out = new Outer(); // 创建一个外部类对象
Outer.Inner in = out.new Inner(); // 创建一个内部类对象(必须依赖外部类对象)创建语法特殊
System.out.println(in.b); // 调用成员内部类的变量
in.xiao(); // 调用成员内部类的方法
// System.out.println(out.a); // 报错,无法访问Outer类的私有属性
// Outer.Inner.field //Error 无法脱离外部类对象进行静态的调用
}
}
class Outer{
private int a = 33;
private String c = "xiao";
class Inner{ // 成员内部类,依赖外部类
// static String field = "abc"; //成员内部类不能定义静态成员
int b = 5;
int a = 66;
public void xiao(){
int a = 99;
System.out.println("xiao一xiao" +" " + c); // 内部类可以直接访问外部类对象
System.out.println(a); //内部类可以直接访问自身的局部变量
System.out.println(this.a); //内部类可以访问内部类的实例变量
System.out.println(Outer.this.a); //内部类可以访问外部类实例变量
}
}
}
静态内部类
public class TestStaticClass {
public static void main(String[] args) {
//外部类的静态属性
System.out.println(Outer.a);
//外部类的静态方法
Outer.m1();
//创建静态内部类对象时,可以直接通过完整名称进行创建
Outer.Inner in = new Outer.Inner(); //静态内部类的创建,不依赖外部类的对象
//创建内部类对象后,访问实例属性和方法
System.out.println(in.b);
in.m2();
//&&额外比成员内部类多了一个定义的内容(静态属性和静态方法)&&
//通过外部类类名.内部类类名.内部类的静态属性
System.out.println(Outer.Inner.field);
//通过外部类类名.内部类类名.内部类的静态方法
Outer.Inner.m3();
}
}
class Outer{ // 外部类
static int a = 10;
static class Inner{ // 静态内部类
int b = 20;
static String field = "abc";
public void m2(){
System.out.println("Inner m2()");
}
public static void m3(){
System.out.println(a);
}
}
static void m1(){
System.out.println("Outer m1()");
}
}
局部内部类
- 特点:
(1)定义在外部类方法中,作用范围和创建对象范围仅限于当前方法
(2)局部内部类访问外部类当前方法中的局部变量是,因无法保障变量的生命周期与自身相同,变量必须修饰为final
(3)限制类的使用范围
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
这段代码为两个按钮设置监听器,这里面就使用了匿名内部类。这段代码中的:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也是不能有访问修饰符和static修饰符的。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。