可以将一个类的定义放在另一个类的定义内部,这就是内部类.
1. 创建内部类
|
|
2. 链接到外部类
当生成一个内部类对象时,此对象与制造它的外围对象(enclosing object)
之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件.此外,内部类还拥有其外围类的所有元素的访问权.
内部类自动拥有对其外围类所有成员的访问权?
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用.然而,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员.幸运的是 : 编译器会帮你处理所有的细节,但你现在可以看到 : 内部类的对象只能在与其外围类的对象相关联的情况下才会被创建.构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错!
3. 使用.this和.new
如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this.这样产生的引用自动地具有正确的类型.
有时你可能想要告知某些其他对象,去创建某个内部类的对象.要实现此目的,你必须用new表达式中提供对其他外部类对象的引用,这就需要使用.new语法.
|
|
在拥有外部类对象之前是不可能创建内部类对象的,这是因为内部类对象会暗暗连接到创建它的外部类对象上.但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用.
4. 内部类向上转型
当将内部类向上转型为基类,尤其是转型为一个接口的时候,内部类就有了用武之地.(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的.)
5. 在方法和作用域的内部类
第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类.这被称为局部内部类.
|
|
6. 匿名内部类
|
|
contents()
方法将返回值的生成与表示这个返回值的类的定义结合在一起!这种奇怪的语法指的是 : 创建一个继承自Contents的匿名类的对象.通过 new 表达式返回的引用被自动向上转型为Contents的引用.
|
|
6.1 再访工厂方法
|
|
7. 嵌套类
如果不需要内部类对象与外围类对象之间有联系,可以将内部类声明为static.这通常称为嵌套类.我们需要明白 : 普通的内部类对象隐私地保存了一个引用,指向创建它的外围类对象.然而,当内部类是static时,就不是这样了.
- 要创建嵌套类的对象,并不需要其外围类的对象.
- 不能从嵌套类的对象中访问非静态的外围类对象.
嵌套类与普通的内部类还有一个区别.普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类.但是嵌套类可以包含这些东西 :
|
|
7.1 接口内部的类
|
|
7.2 从外层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要,它能透明地访问所有它所嵌入的外围类的所有成员.
|
|
可以看到在MNA.A.B中,调用方法g()和f()不需要任何条件(即使它们被定义为private),这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法..new
语法能产生正确的作用域,所以不必在调用构造器时限定类名.
8. 为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象.所以可以认为内部类提供了某种进入某外围类的窗口.
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响.
单一类与内部类的比较 :
|
|
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立.
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类.
- 创建内部类对象的时刻并不依赖于外围类对象的创建.
- 内部类没有令人迷惑的”is-a”关系;它就是一个独立的实体.
8.1 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向对其外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private.
8.2 内部类与控制框架
设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模块方法是保持不变的事物,而可覆盖的方法就是变化的事物.
9. 内部类的继承
|
|
InheritInner只继承内部类,而不是外围类.但要生成一个构造器时,默认的构造器并不友好,而且不能只是传递一个指向外围类对象的引用.因此,必须在构造器内使用如下语法 : enclosingClassReference.super()
,只有提供必要的引用,然后程序才能编译通过.
10. 内部类可以被覆盖吗
|
|
11. 局部内部类
|
|
12. 内部类标识符
外围类的名字,加上$
,再加上内部类的名字.