10.1 创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体地指定这个对象的类型:OuterClassName.InnerClassName.
10.2 链接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有外围类的所有元素的访问权。
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,当你访问此外围类的成员时,就是用那个引用来选择外围类的成员。因此内部类对象只能在与其外围类的对象相关联情况下才能被创建(在内部类非static时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不了这个引用就会报错。不过绝大多数时候这都无需程序员操心。
10.3 使用.this与.new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟原点和this.
如果你需要创建某个内部类对象,你必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new语法。如下所示:
//: innerclasses/DotNew.java // Creating an inner class directly using the .new syntax. public class DotNew { public class Inner {} public static void main(String[] args) { DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner(); } } ///:~
在拥有外部类对象之前是不能创建内部类对象的。如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
10.4 内部类与向上转型
当内部类向上转型为其积累,尤其是转型为一个接口的时候,内部类就有了用武之地。private内部类给类的设计者提供了一个途径,通过这种方式可以完全阻止任何依赖于类型的代码,并且完全隐藏了实现的细节。
//: innerclasses/TestParcel.java class Parcel4 { private class PContents implements Contents { private int i = 11; public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination destination(String s) { return new PDestination(s); } public Contents contents() { return new PContents(); } } public class TestParcel { public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents c = p.contents(); Destination d = p.destination("Tasmania"); // Illegal -- can't access private class: //! Parcel4.PContents pc = p.new PContents(); } } ///:~
10.5 在方法与作用域内的内部类
可以在一个方法里面或者在任意的作用域内定义内部类。这么做有两个理由:
1)如前所述,你实现了某个类型的接口,于是可以创建并返回对其的引用。
2)你要解决一个复杂问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
若某个类在if语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然后,在定义该类的作用域外,它是不可用的;除此以外,它与普通类一样。
10.6 匿名内部类
在匿名类中定义字段时,还能够对它执行初始化操作。如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的,如果你忘记了,会得到一个编译时错误。
匿名内部类不可能有命名构造器,但是通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果:
//: innerclasses/Parcel10.java // Using "instance initialization" to perform // construction on an anonymous inner class. public class Parcel10 { public Destination destination(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: { cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel10 p = new Parcel10(); Destination d = p.destination("Tasmania", 101.395F); } } /* Output: Over budget! *///:~
匿名内部类与正规类的继承相比有些限制,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果实现接口,也只能实现一个接口。
10.7 嵌套类
如果不需要匿名内部类与外围类对象之间的联系,那么可以将内部类申明为static。这通常称为嵌套类。当内部类是static时:
1)要创建嵌套类的对象,并不需要其外围类的对象;
2)不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段和方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但嵌套类可以包含所有这些东西。
10.7.1 接口内部的类
正常情况下,不能在接口内部放置任何代码,但是嵌套类可以作为接口的一部分。你放到接口中的任何类都自动的是static和public的。
如果你想要创建某些公共代码,使得他们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类就会显得方便。
//: innerclasses/ClassInInterface.java // {main: ClassInInterface$Test} public interface ClassInInterface { void howdy(); class Test implements ClassInInterface { public void howdy() { System.out.println("Howdy!"); } public static void main(String[] args) { new Test().howdy(); } } } /* Output: Howdy! *///:~
10.7.2 从多层嵌套类中访问外部类的成员
//: innerclasses/MultiNestingAccess.java // Nested classes can access all members of all // levels of the classes they are nested within. class MNA { private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } } } public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } } ///:~
10.8 为什么需要内部类
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果使用内部类,还可以获得其他一些特性:
1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立;
2)在单个外围类,可以让多个内部类以不同的方式实现同一个接口,或集成同一个类。
3)创建内部类对象的时刻并不依赖于外围类对象的创建;
4)内部类并没有令人迷惑的“si-a”关系,它就是一个独立的个体。
10.9 内部类的继承
问题在于,那个指向外围类对象的“秘密的”引用必须被初始化。
//: innerclasses/InheritInner.java // Inheriting an inner class. class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { //! InheritInner() {} // Won't compile InheritInner(WithInner wi) { wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } ///:~
10.10 内部类可以被覆盖吗
当继承了某个外围类的时候,内部类并没有发生什么神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。
//: innerclasses/BigEgg2.java // Proper inheritance of an inner class. import static net.mindview.util.Print.*; class Egg2 { protected class Yolk { public Yolk() { print("Egg2.Yolk()"); } public void f() { print("Egg2.Yolk.f()");} } private Yolk y = new Yolk(); public Egg2() { print("New Egg2()"); } public void insertYolk(Yolk yy) { y = yy; } public void g() { y.f(); } } public class BigEgg2 extends Egg2 { public class Yolk extends Egg2.Yolk { public Yolk() { print("BigEgg2.Yolk()"); } public void f() { print("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } } /* Output: Egg2.Yolk() New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f() *///:~
10.11 局部内部类
可以在代码块里面创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它表示外围类的一部分,但是它可以访问当前代码块内的常量,以及外围类的所有成员。
10.12 内部类标识符
由于每个类都会产生一个.class文件。内部类生成的.class文件的命名有严格的规则:外围类的名字,加上“$”,再加上内部类的名字。