一、概述
类的五大成员:属性,方法,构造方法,代码块,内部类
1.引言:
当需要描述一个汽车类,属性有汽车品牌,车龄,颜色,发动机品牌,发动机使用年限
但是发动机应该是一个独立的个体,不应该和汽车的属性定义在一起,发动机应该自成一个类
但发动机类是需要依赖汽车类才能存在的,离开了汽车单独出现就失去了实际意义 ,所以需要把发动机类定义在汽车类内部
2.定义:
在一个类里面定义的类就叫做内部类
3.访问特点
①内部类可以直接访问外部类的成员,包括私有
②外部类要访问内部类的成员,就必须创建对象
外部类访问carName,实际隐含了个this,this表示方法调用者的地址值。
在测试类创建Car对象后,this指向该对象,该对象中有carName成员变量,但是没有engineName这个成员变量。必须要创建Engine对象,只有Engine对象里才有engineName成员变量。
4.应用场景:
B类事物表示A类事物的一部分,且B单独存在没有意义 。
比如:汽车的发动机,ArrayList的迭代器,人的心脏
二、分类
1.成员内部类:
写在成员位置的,属于外部类的成员
(1)注意事项:
① 成员内部类和外部类的成员变量,成员方法地位一样,都可以被一些修饰符所修饰,比如private,缺省,protected,public,static等
② 在成员内部类中,JDK16以前是不能定义静态变量的,JDK16开始才可以定义静态变量
(2)获取成员内部类对象的两种方式:
①直接创建(内部类非private修饰时常用)
②外部类编写方法,对外提供内部类对象(内部类private修饰时使用)
由于内部类私有,无法直接用Inner类型接收对象,可采用以下两种办法:
(3)成员内部类获取外部类的成员变量
Question:
解决办法:通过 Outer.this.变量名进行访问
内存分析:
首先,在加载字节码文件时,内部类和外部类的字节码文件都会加载到方法区,且不是同一个文件名。内部类的字节码文件名是 外部类名$内部类名.class
在内部类的字节码文件中,成员变量除了定义的变量 a ,还有一个隐含的 Outer类型的 this ,为了和代表方法调用者的 this 区分开,实际名字为 this$0 ,这个 this$0 指向外部类对象
内存图:
由于就近原则,a 会首先找到方法内的局部变量a
this代表方法调用者的地址值,所以this.a 会访问到本类 Inner的成员变量a
通过 Outer.this 来访问外部类的实例时,编译器会将其转换为对应的 this$0 引用找到外部类对象,在访问其成员变量a
★ 虽然在内部类中实际存储的是指向外部类实例的引用 this$0 ,但通过 Outer.this 语法,我们可以更直观地理解这个引用指向的是外部类的实例。
2.静态内部类:
用static修饰的成员内部类称为静态内部类
(1)注意事项:
①静态内部类也是成员的内部类的一种
②静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的必须要创建对象。(静态只能访问静态)
(2)创建静态内部类对象的格式:
(3)调用静态内部类中的方法:
① 调用非静态方法:先创建对象,再用对象调用
② 外部类名.内部类名.方法名();
注意new的对象不是Outer,而是Outer.Inner
3.局部内部类
定义在方法中的内部类,就叫做局部内部类,类似于方法中的局部变量
注意事项:
① 外界是无法使用局部内部类的,需要在方法内部创建对象并使用。
② 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
③ 修饰局部变量的关键字也可以拿来修饰局部内部类(如 final等),但是其他的关键字就不行(如public,static等)
理解:跟局部变量的性质基本一样,外界无法访问方法内的局部变量,方法内也可以获取外界定义的变量
运行结果:
4.匿名内部类
隐藏了名字的内部类,可以写再成员位置,也可以写在局部位置。
所谓匿名,指的是程序员不需要为这个类声明名字。
作用:更方便的创建出一个子类对象
(1)定义格式:
包含了继承或实现,方法重写,创建对象三部分。
整体就是一个类的子类对象或接口的实现类对象,匿名内部类事实上只是 { } 代表的内容
匿名内部类编译时也会产生字节码文件:
可以看出,匿名内部类并非真的没有名字,只不过不需要自己命名而已,名字为 外部类$序号
验证:通过反编译查看匿名内部类的详细信息
因为整体事实上是一个匿名内部类对象,所以可以直接调用该对象的方法,或直接将该对象赋值给变量
(2)主要使用场景:
通常作为一个参数传递给方法
当方法的参数是接口或类时,
以接口为例,可以传递这个接口的实现类对象,
如果实现类只要使用一次,就可以使用匿名内部类简化代码。
正常做法:单独创建一个子类继承该父类,然后创建子类对象,再传递给method方法(太麻烦)
使用匿名内部类:直接将匿名内部类作为函数的实参使用,利用多态的的思想,Animal a = 子类对象;
注意:实际应用中,匿名内部类我们不会主动去用,而是需要的时候才会去用
举个🌰🌰🌰
给一个按钮绑定单击事件监听器,需要创建一个监听器对象。
通过源码发现,监听器是一个接口。
所以,要么创建一个类实现该接口,但不同按钮的点击事件肯定不一样,即创建的实现类只能用一次,太麻烦也没必要。
要么就直接采用匿名内部类直接使用
所以,在使用API时,而参数是一个对象,而该对象正好是个接口类型,往往就需要使用匿名内部类了。