内部类和组合的概念完全不同。最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,内部类远不止如此,它了解外围类,并能与之通信;而且用内部类写出的代码更加优雅而清晰。
10.1
如果想从外部类的非静态方法之外的任意位置创建内部类的对象,那么必须具体指明这个对象的类型:OuterClass.InnerClass。我的理解:内部类的对象可以看作是和外部类的对象相联的。没有外部类的对象,无法创建内部类的对象。
10.2
内部类的对象只能在与其外围类的对象相关联的情况下才能被创建,构建内部类对象时,需要一个指向其外围类对象的引用。
10.3
在内部类中如果想使用外部类对象的引用,可以使用"OuterClass.this"的形式。此引用自动的具有正确的类型,这一点在编译期被知晓并受到检查,因此没有任何运行时开销。
如果想要告知某些其他对象,去创建某个内部类的对象,必须在new表达式中提供对其外部类对象的引用:
Outer outer = new Outer();
outer.new Inner();
要想直接创建内部类的对象,不能引用外部类的名字,而必须使用外部类的对象来创建该内部类对象。在拥有外部类对象之前是不可能创建内部类对象事务。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。但是如果创建的是嵌套类(静态内部类),就不需要外部类对象的引用。
10.4
练习6中在类中的一个protected内部类,在另外包的类中是无法创建对象的,因为该内部类的构造函数和内部类拥有相同的访问属性,即protected。解决方法是显示写出内部类的构造函数,并把它设置为public。
此处需要注意的特性是,内部类的构造函数和内部类具有相同的访问属性。
练习8告诉我们,外部类可以访问内部类的private域和方法。内部类和外部类的访问权限是双向的。
10.5
可以在一个方法里或者在任意的作用域内定义内部类。这么做有两个理由:
(1)实现了某个接口,于是可以创建并返回对其的引用;
(2)要解决一个复杂的问题,想创建一个类来辅助,但又不希望这个类是公共可用的。
我的理解:这种在方法或作用域中的内部类与其他类一起被编译,并不是说只有到作用域里才编译。但是,它们只在作用域中可见。
匿名类不可能有构造函数。
10.6
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。
由于匿名内部类没有构造函数,可以用语句块的形式对类的对象进行初始化。
练习12 要求在没有指定实现的接口或继承的基类的情况下返回一个匿名内部类,这时可用Object作为引用:
public Object getInnerClass() { return new Object() { ... }}
运用匿名内部类,修改接口那一章的工厂方法,使代码变得更整洁优雅:
10.7
静态的内部类也没称为嵌套类,它与普通内部的区别:
(1)要创建嵌套类的对象,并不需要外围类的对象;
(2)不能从嵌套类的对象中访问非静态的外围类数据;
(3)嵌套类可以有static方法和域,还可以有嵌套类,普通内部类不可以。
练习18 要求创建一个嵌套类并在main函数中创建其实例。答案说,如果在外围类中使用嵌套类,直接使用类名就可以,但是如果在其他地方使用嵌套类,则必须使用“外围类.嵌套类”的形式。经过代码测试,其实直接“import 外围类.嵌套类”也可以在其他地方使用嵌套类。
接口中可以包含嵌套类。放到接口中的任何类都自动的是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,并不违反接口的规则。甚至可以在嵌套类中实现其外围接口。如果想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共有,那么使用接口内部的嵌套类会很方便。
作者还提到了一个技巧:
他建议在每个类中都写一个主函数用来测试这个类。这样有一个缺点,是必须带有这些额外的测试代码。可以使用嵌套类来放置主函数和测试代码。比如在类TestBed中创建Tester嵌套类,包含主函数和测试代码。那么编译器会生成一个独立的类TestBed$Tester,要运行这个程序,执行java TestBed$Tester(Unix/Linux系统中需要转义$)。当发布产品的时候,简单的删除TestBed$Tester.class文件即可。
经编码测试接口中的嵌套类是不会继承到它的实现中的。
10.7.2小节主要讲了,多层内部类,不论层数有多少,它都能透明的访问所有外围类。个人认为这个讨论不应放在10.7中,因为本节主要讲述嵌套类(即静态的内部类),而此处说的是普通内部类,容易造成混淆。此处应注意,10.7.2小节讨论的是普通内部类的嵌套,而不是嵌套类。
10.8
使用内部类的一个主要原因是每个内部类都能独立的继承一个类,而无论外围类是否已经继承了某个类。这样内部类和接口一起构成了多重继承的解决方案。
还有其他一些特性:
(1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互对立;
(2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类;
(3)书中有些迷惑。英语原话是:The point of creation of the inner-class object is not tied to the creation of the outer-class object. 我的理解是,内部类对象创建的时间点不与外部类对象的创建绑定在一起。这应该是相对于导出类与基类的关系来说的(因为导出类的对象创建时基类的对象也将创建);
(4)内部类并没有令人迷惑的“is-a”关系,它是一个独立的实体。
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。按照这个定义,内部类是一种面向对象的闭包。
回调机制中的对象携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。C++中通过函数指针可以实现回调,而Java通过内部类提供的闭包功能来实现回调。回调的价值在于它的灵活性——可以在运行时动态的决定需要调用什么方法。在实现GUI功能的时候,到处都用到了回调。
10.9
继承内部类时,语法比较特殊:
class WithInner { class Inner { }}public class InheritInner extends WithInner.Inner { //默认的构造方法不会编译通过 //构造函数必须有一个外部类的引用参数 public InheritInner(WithInner wi){ wi.super(); //我的理解:可以看做是显示的调用Inner的构造函数 }}
10.10
当继承了某个外围类的时候,内部类并没有发生什么变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。
10.11
方法或作用域内的内部类(局部内部类)可以访问当前代码块内的常量以及外围类的所有变量。
既然局部内部类的名字在作用域外是不可见的,那为什么不适用匿名内部类?有两个原因:
(1)需要构造函数;
(2)需要多个该内部类的实例。
10.12
每个类会产生一个.class文件,其中包含了如何创建该类型对象的全部信息(此信息产生一个meta-class,叫做Class对象)。内部类的命名规则:外围类的名字加上$,再加上内部类的名字。如果内部类是匿名的,编译器会简单的产生一个数字作为其标识符。