本文介绍了 Java 中的内部类、匿名内部类以及嵌套类机制。
1. 创建内部类
创建内部类的方式就是把类定义放在一个包围它的类之中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.yyj;public class Parcel1 { class Contests { private int x = 1 ; public int value () { return x; } } class Destination { private String dest; Destination(String dest) { this .dest = dest; } String getDest () { return dest; } } public void ship (String dest) { Contests c = new Contests (); Destination d = new Destination (dest); System.out.println(d.getDest()); } public static void main (String[] args) { Parcel1 p = new Parcel1 (); p.ship("XDU" ); } }
2. 内部类到外部类的连接
到目前为止,内部类看上去就是一种名称隐藏和代码组织机制。当创建一个内部类时,这个内部类的对象中会隐含一个链接,指向用于创建该对象的外围对象。通过该链接,无须任何特殊条件,内部类对象就可以访问外围对象的成员。此外,内部类拥有対外围对象所有元素的访问权:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.yyj;interface Selector { boolean end () ; Object current () ; void next () ; } public class Sequence { private Object[] objs; private int now = 0 ; public Sequence (int size) { objs = new Object [size]; } public void add (Object x) { if (now < objs.length) objs[now++] = x; } private class SequenceSelector implements Selector { private int i = 0 ; @Override public boolean end () { return i == objs.length; } @Override public Object current () { return objs[i]; } @Override public void next () { if (i < objs.length) i++; } } public Selector getSelector () { return new SequenceSelector (); } public static void main (String[] args) { Sequence seq = new Sequence (10 ); for (int i = 0 ; i < 10 ; i++) seq.add(Integer.toString(i)); Selector s = seq.getSelector(); while (!s.end()) { System.out.print(s.current() + " " ); s.next(); } } }
Sequence
是以类的形式包装起来的定长 Object
数组,可以调用 add()
向序列末尾增加一个新的 Object
(如果还有空间)。要取得 Sequence
中的每一个对象,可以使用名为 Selector
的接口,这是迭代器(Iterator)设计模式的一个例子。
SequenceSelector
中的 end()
、current()
和 next()
这些方法中的每一个都用到了外部类的字段 objs
,这个引用并不是 SequenceSelector
的一部分,而是外围对象的一个 private
字段。然而,内部类可以访问外围对象的所有方法和字段,就好像拥有它们一样。
这是怎么做到的呢?对于负责创建内部类对象的特定外围类对象而言,内部类对象偷偷地获取了一个指向它的引用。然后,当你要访问外围类的成员时,该引用会被用于选择相应的外围类成员。幸运的是,编译器会为你处理所有的这些细节。
3. 在内部类中生成外部类对象的引用
要生成外部类对象的引用,可以使用外部类的名字,后面加上 .this
。这样生成的引用会自动具有正确的类型,而且是可以在编译时确定并检查的,所以没有任何运行时开销,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.yyj;public class DotThis { void f () { System.out.println("DotThis.f()" ); } public class Inner { public DotThis outer () { return DotThis.this ; } } public Inner getInner () { return new Inner (); } public static void main (String[] args) { DotThis dt = new DotThis (); DotThis.Inner dti = dt.getInner(); dti.outer().f(); } }
有时我们想让其他某个对象来创建它的某个内部类的对象,要实现这样的功能,可以使用 .new
语法,在 new
表达式中提供指向其他外部类对象的引用,就像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.yyj;public class DotNew { public class Inner { void f () { System.out.println("Inner.f()" ); } } public static void main (String[] args) { DotNew dn = new DotNew (); DotNew.Inner dni = dn.new Inner (); dni.f(); } }
我们要使用外部类的对象来创建内部类的对象,正如我们在示例代码中所看到的那样,这也解决了内部类的名字作用域问题。
除非已经有了一个外部类的对象,否则创建内部类对象是不可能的,这是因为内部类的对象会暗中连接到用于创建它的外部类对象。然而,如果你创建的是嵌套类(static
修饰的内部类),它就不需要指向外部类对象的引用。
4. 匿名内部类
我们先来看一下如何创建匿名内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.yyj;interface AnonymousIf { int value () ; } public class AnonymousClass { public AnonymousIf getAnanymousIf () { return new AnonymousIf () { private int x = 1 ; @Override public int value () { return x; } }; } public static void main (String[] args) { AnonymousClass ac = new AnonymousClass (); AnonymousIf ai = ac.getAnanymousIf(); System.out.println(ai.value()); } }
这段代码的意思是创建一个继承自 AnonymousIf
的匿名类的对象,通过 new
表达式返回的引用会被自动地向上转型为一个 AnonymousIf
引用。
在这个匿名内部类中,AnonymousIf
是用无参构造器创建的,如果基类需要带一个参数的构造器,可以这么做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.yyj;class BaseAnonyClass { private int x; BaseAnonyClass(int value) { x = value; System.out.println("Construct BaseAnonyClass with " + value); } int value () { return x; } } public class AnonymousClass { public BaseAnonyClass getBaseAnonyClass (int value, final String str) { return new BaseAnonyClass (value) { private String name; { name = str; System.out.println("Anonymous class constructor" ); } @Override public int value () { return super .value() * 2 ; } @Override public String toString () { return name; } }; } public static void main (String[] args) { AnonymousClass ac = new AnonymousClass (); BaseAnonyClass b = ac.getBaseAnonyClass(10 , "AsanoSaki" ); System.out.println(b.value()); System.out.println(b); } }
也可以在定义匿名类中的字段时执行初始化,如果你正在定义一个匿名类,而且一定要用到一个在该匿名类之外定义的对象,编译器要求参数引用需要用 final
修饰,或者是“实际上的最终变量”(也就是说,在初始化之后它永远不会改变,所以它可以被视为 final
的)。这里变量 value
被传给了匿名类的基类构造器,并没有在匿名类的内部被直接用到,因此不是必须为 final
变量。
如果必须在匿名类中执行某个类似构造器的动作,该怎么办呢?因为匿名类没有名字,所以不可能有命名的构造器。我们可以借助实例初始化,使用 {}
语句块在效果上为匿名内部类创建一个构造器。
5. 嵌套类
如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为 static
的,我们通常称之为嵌套类 。要理解 static
应用于内部类时的含义,请记住,普通内部类对象中隐式地保留了一个引用,指向创建该对象的外部类对象,对于 static
的内部类来说,情况就不是这样了,嵌套类意味着:
不需要一个外部类对象来创建嵌套类对象。
无法从嵌套类对象内部访问非 static
的外部类对象。
从另一方面来看,嵌套类和普通内部类还有些不同。普通内部类的字段和方法,只能放在类的外部层次中,所以普通内部类中不能有 static
数据、static
字段,也不能包含嵌套类,但是嵌套类中可以包含所有这些内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.yyj;public class NestedClass { private static class StaticInner { static int x = 10 ; public static void f () { System.out.println("StaticInner static f()" ); } StaticInner() { System.out.println("Construct StaticInner" ); } static class AnotherStaticClass { public static void f () { System.out.println("AnotherStaticClass static f()" ); } } } public static void main (String[] args) { StaticInner si = new StaticInner (); StaticInner.f(); StaticInner.AnotherStaticClass.f(); } }
可以看到我们在 main()
中并不需要创建 NestedClass
对象就能直接创建 static
的内部类对象。
6. 接口中的类
嵌套类可以是接口的一部分,放到接口中的任何类都会自动成为 public
和 static
的,因为类是 static
的,所以被嵌套的类只是放在了这个接口的命名空间内,甚至可以在内部类内实现包围它的这个接口,就像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.yyj;public interface ClassInInterface { void f () ; class Inner implements ClassInInterface { @Override public void f () { System.out.println("ClassInInterface.Inner f()" ); } public static void main (String[] args) { new Inner ().f(); } } }