内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。
当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
注意:当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 static 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。
在内部类中获取外围类的引用:可以使用外部类的名字后面紧跟圆点和 this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。
// innerclasses/DotThis.java // Accessing the outer-class object public class DotThis { void f() { System.out.println("DotThis.f()"); } public class Inner { public DotThis outer() { return DotThis.this;//返回外部类引用 // A plain "this" would be Inner's "this" } } public Inner inner() { return new Inner(); } public static void main(String[] args) { DotThis dt = new DotThis(); DotThis.Inner dti = dt.inner(); dti.outer().f(); } }
输出:
DotThis.f()
创建内部类:去创建其某个内部类的对象。你必须在 new 表达式中提供对其他外部类对象的引用,这是需要使用 .new 语法,
// innerclasses/DotNew.java // Creating an inner class directly using .new syntax public class DotNew { public class Inner {} public static void main(String[] args) { DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner();//创建内部类对象 } }
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
在这里插入代码片// innerclasses/Destination.java public interface Destination { String readLabel(); }
// innerclasses/Contents.java public interface Contents { int value(); }
// innerclasses/TestParcel.java class Parcel4 { private class PContents implements Contents { private int i = 11; @Override public int value() { return i; } } protected final class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } @Override 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(); 编译过程中就会报错 } }
Parcel4 中,内部类 PContents 是 private,所以除了 Parcel4,没有人能访问它。普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 public 或 package 访问权限。
PDestination 是 protected,所以只有 Parcel4 及其子类、还有与 Parcel4 同一个包中的类(因为 protected 也给予了包访问权)能访问 PDestination,其他类都不能访问 PDestination,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成 private 内部类(或 protected 内部类,除非是继承自它的子类),因为不能访问其名字,就像在 TestParcel 类中看到的那样。
// innerclasses/Parcel7.java // Returning an instance of an anonymous inner class public class Parcel7 { public Contents contents() { return new Contents() { // Insert class definition private int i = 11; @Override public int value() { return i; } }; // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents c = p.contents(); } }
“创建一个继承自 Contents 的匿名类的对象。”通过 new 表达式返回的引用被自动向上转型为对 Contents 的引用。上述匿名内部类的语法是下述形式的简化形式:
// innerclasses/Parcel7b.java // Expanded version of Parcel7.java public class Parcel7b { class MyContents implements Contents { private int i = 11; @Override public int value() { return i; } } public Contents contents() { return new MyContents(); } public static void main(String[] args) { Parcel7b p = new Parcel7b(); Contents c = p.contents(); } }
匿名类有参构造
// innerclasses/Parcel8.java // Calling the base-class constructor public class Parcel8 { public Wrapping wrapping(int x) { // Base constructor call: return new Wrapping(x) { // [1] @Override public int value() { return super.value() * 47; } }; // [2] } public static void main(String[] args) { Parcel8 p = new Parcel8(); Wrapping w = p.wrapping(10); } }
1] 将合适的参数传递给基类的构造器。
[2] 在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 static,这通常称为嵌套类。想要理解 static 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 static 的时,就不是这样了。嵌套类意味着:
要创建嵌套类的对象,并不需要其外围类的对象。
不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:
如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private 成员。
// innerclasses/Callbacks.java // Using inner classes for callbacks // {java innerclasses.Callbacks} package innerclasses; interface Incrementable { void increment(); } // Very simple to just implement the interface: class Callee1 implements Incrementable { private int i = 0; @Override public void increment() { i++; System.out.println(i); } } class MyIncrement { public void increment() { System.out.println("Other operation"); } static void f(MyIncrement mi) { mi.increment(); } } // If your class must implement increment() in // some other way, you must use an inner class: class Callee2 extends MyIncrement { private int i = 0; @Override public void increment() { super.increment(); i++; System.out.println(i); } private class Closure implements Incrementable { @Override public void increment() { // Specify outer-class method, otherwise // you'll get an infinite recursion: Callee2.this.increment(); } } Incrementable getCallbackReference() { return new Closure(); } } class Caller { private Incrementable callbackReference; Caller(Incrementable cbh) { callbackReference = cbh; } void go() { callbackReference.increment(); } } public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(); Callee2 c2 = new Callee2(); MyIncrement.f(c2); Caller caller1 = new Caller(c1); Caller caller2 = new Caller(c2.getCallbackReference()); caller1.go(); caller1.go(); caller2.go(); caller2.go(); } }
输出:
Other operation 1 1 2 Other operation 2 Other operation 3
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:
// 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); } }
可以看到,InheritInner 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:
enclosingClassReference.super();
当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。
局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。
// innerclasses/LocalInnerClass.java // Holds a sequence of Objects interface Counter { int next(); } public class LocalInnerClass { private int count = 0; Counter getCounter(final String name) { // A local inner class: class LocalCounter implements Counter { LocalCounter() { // Local inner class can have a constructor System.out.println("LocalCounter()"); } @Override public int next() { System.out.print(name); // Access local final return count++; } } return new LocalCounter(); } // Repeat, but with an anonymous inner class: Counter getCounter2(final String name) { return new Counter() { // Anonymous inner class cannot have a named // constructor, only an instance initializer: { System.out.println("Counter()"); } @Override public int next() { System.out.print(name); // Access local final return count++; } }; } public static void main(String[] args) { LocalInnerClass lic = new LocalInnerClass(); Counter c1 = lic.getCounter("Local inner "), c2 = lic.getCounter2("Anonymous inner "); for(int i = 0; i < 5; i++) System.out.println(c1.next()); for(int i = 0; i < 5; i++) System.out.println(c2.next()); } }
由于编译后每个类都会产生一个.class 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 Class 对象)。
你可能猜到了,内部类也必须生成一个.class 文件以包含它们的 Class 对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“$",再加上内部类的名字。例如,LocalInnerClass.java 生成的 .class 文件包括:
Counter.class LocalInnerClass$1.class LocalInnerClass$LocalCounter.class LocalInnerClass.class