内部类(inner class)
内部类是指定义在另一个类中的类。
访问外部类对象
内部类方法可以访问该类定义所在的作用域中的变量,包括私有变量。
如上代码所示,类B可以访问私有变量isTrue
。内部类既可以访问自身数据域,也可以访问创建它的外围类对象的数据域。划重点:因为内部类的对象总有一个隐式引用outer,指向创建它的外部类对象,这个引用在内部类的定义中不可见。
外围类的引用在构造器中设置,编译器修改了内部类的构造器,添加了一个外围类引用参数,如下代码所示:
事实上,使用外围类引用的语法的表达式是:OutClass.this
,例如:
内部类的安全性
内部类的语法很复杂,需要管理额外的访问权限。内部类是一种编译器现象,与虚拟机无关,编译器会把内部类翻译成常规类文件,命名为OutClass$InnerClass
,如A$B
。编译器为了引用外围类,生成了一个附加的类A实例域,同时在类B构造器中加入类A的引用参数。
那么内部类如何管理访问权限呢?
编译器在外围类添加静态方法access$0
(方法名可能不同,取决于编译器),在类B中的调用方法即access$0(outer)
。这样做存在安全风险,任何人都可以通过调用access$0
方法读取到私有域isTrue
。
局部内部类
作用域
在一个方法中定义内部类,不能用public
或private
访问说明符声明,作用域被限定在声明这个局部类的块中,对外部世界完全隐藏。
不能含有静态成员
局部内部类不能含有静态成员,内部类的实例对象是外部类的对象的一个成员,若没有外部对象,内部对象也是不存在的。
访问局部变量
与其他内部类相比,局部类还有一个优点,不仅可以访问包含它们的外部类,还可以访问局部变量,但是局部变量必须被声明为final
。
局部变量的生命周期和局部内部类对象的生命周期不一致,局部变量在所在函数执行完后就被释放了,而局部内部类对象还可能一直存在,只要局部内部类的对象一直被引用。这样就会存在局部内部类的对象访问一个已经被释放的变量。当局部变量被声明为final
后,编译器在编译时将该变量写入局部内部类中,作为成员变量,声明为final
为了保持创建之后和外部类的变量一致,叫做capture-by-value
。局部内部类编译后:(变量名可能有所不同)
关于为什么局部变量必须被声明为final
,可以参考”为什么必须是final的呢?
“这篇博客。
在JDK1.8之后, 局部内部类和匿名内部类访问的局部变量可以不用final
修饰了,Java引入了effectively final
的概念(A variable or parameter whose value is never changed after it is initialized is effectively final), 看起来是编译器取消了这种机制,实际上是诸如effectively final
的变量或参数被Java默认为final
类型,所以才不会报错。
静态方法的局部内部类
静态方法没有this引用,只能访问静态成员,静态方法的局部内部类同样也只能访问外部类的静态成员。
匿名内部类
匿名内部类是在局部内部类的使用上更深入一步。匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们。这就要采用另一种形式的new语句,如下所示:
|
|
其中SuperType
可以是接口名称,所以内部类就要实现该接口,SuperType
也可以是一个类,内部类就要扩展它。
由于构造器名字必须与类名相同,而匿名类没有名称,所以匿名类没有构造器,取而代之,将构造器参数传递给超类构造器。所以在实现接口时,不能有任何参数。如下所示:
|
|
使用匿名内部类可以简化代码,让代码简短,更切实际,更易理解。
注意:匿名类不等同于匿名内部类
静态内部类
有时候内部类只是为了把一个类隐藏在另一个类里面,并不需要引用外部类的对象,此时可以将内部类声明为static
,以便取消引用。注意:只有内部类可以声明为static
。
同时注意以下几点:
- 静态内部类可以有静态成员,而非静态内部类则不能有静态成员
- 静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量
参考信息
- Java核心技术卷I
- java为什么匿名内部类的参数引用时final?
- 静态内部类和非静态内部类的区别