内部类

可以将一个类的定义放在另一个类的定义内部,这就是内部类。它允许把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。

创建内部类

如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体指出这个对象的类型:OuterClassName.InnerClassName

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
class A {

class AA {

AA(String s) {
System.out.println(s + ": AA");
}
}

public void f() {
AA aa = new AA("f");
}

public static void sf() {
A a = new A();
AA aa = a.new AA("sf");
}

public AA gf() {
return new AA("gf");
}
}

new A().f(); // f: AA
A.sf(); // sf: AA
A.AA aa = new A().gf(); // gf: AA

链接到外部类

当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在访问此外围类的成员时,就是用那个引用来选择外围类的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A {

private String s;

A(String s) {
this.s = s;
}

class AA {

void f() {
// 内部类自动拥有对其外围类所有成员的访问权
System.out.println(s);
}
}

public AA getAA() {
return new AA();
}
}

A a = new A("A");
A.AA aa = a.getAA();
aa.f(); // A

使用 .this 和 .new

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟 .this,且会在编译时受到检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {

class AA {

A getA() {
return A.this;
}
}

public AA getAA() {
return new AA();
}
}

A a = new A();
System.out.println(a.toString()); // A@1b6d3586
A.AA aa = a.getAA();
System.out.println(aa.getA()); // A@1b6d3586

想要直接创建内部类的对象,必须使用外部类的对象来创建该内部类的对象,因为内部类对象会暗暗地连接到创建它的外部类对象上。使用 .new 来直接创建内部类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
class A {

class AA {

AA() {
System.out.println("AA");
}
}
}

A a = new A();
A.AA aa = a.new AA(); // AA

如果创建的是 嵌套类(静态内部类),那么就不需要对外部类对象的引用。

1
2
3
4
5
6
7
8
9
10
11
class A {

static class AA {

AA() {
System.out.println("AA");
}
}
}

A.AA aa = new A.AA(); // A

内部类与向上转型

将内部类向上转型为其基类,尤其是转型为一个接口的时候,得到的只是指向其基类或者接口的引用,所以能够很方便地隐藏实现细节。例如 private 内部类,对于客户端程序员来说,完全隐藏了实现的细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface B {
void f();
}

class A {

private class AA implements B {

@Override
public void f() {
System.out.println("AA");
}
}

public B getB() {
return new AA();
}
}

B b = new A().getB();
b.f(); // AA

在方法和作用域内的内部类

可以在一个方法里面或者在任意的作用域内定义内部类,该内部类被称为 局部内部类。这样做有两个理由:

  • 实现某类型的接口,创建并返回对其的引用
  • 创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 方法的作用域内

interface B {
void f();
}

class A {

public B getB() {

class AA implements B {

@Override
public void f() {
System.out.println("AA");
}
}

return new AA();
}
}

B b = new A().getB();
b.f(); // AA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 在任意的作用域内

class A {

public void s(boolean b) {

if (b) {

// AA 在 if() 内,并不是代表类的创建是有条件的
// AA 与普通的类一样,都被编译过,只是在作用域之外,AA 是不可用的
class AA {
public void f() {
System.out.println("AA");
}
}

AA aa = new AA();
aa.f();
}
}
}

A a = new A();
a.s(true); // AA

匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface B {
void f();
}

class A {

public B getB() {

// 创建一个继承自 B 的匿名类对象
// 通过 new 表达式返回的引用被自动向上转型为对 B 的引用
return new B() { // 默认无参构造器

@Override
public void f() {
System.out.println("B");
}
};
}
}

B b = new A().getB();
b.f(); // B
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
// 存在有参数的构造器

class B {

String s;

B(String s) {
this.s = s;
}

void f() {
}
}

class A {

public B getB(String s) {

// 尽管 B 只是一个普通类,但还是被其导出类当作公共接口来使用
return new B(s) {

@Override
void f() {
System.out.println(s);
}
};
}
}

B b = new A().getB("B");
b.f(); // B
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
// 使用匿名内部类的工厂方法

interface Service {
void f();
}

interface ServiceFactory {
Service getService();
}

class Factory {

public static void ServiceConsumer(ServiceFactory serviceFactory) {
Service service = serviceFactory.getService();
service.f();
}
}

class A implements Service {

@Override
public void f() {
System.out.println("A");
}

public static ServiceFactory serviceFactory = new ServiceFactory() {

@Override
public Service getService() {
return new A();
}
};
}

class B implements Service {

@Override
public void f() {
System.out.println("B");
}

public static ServiceFactory serviceFactory = new ServiceFactory() {

@Override
public Service getService() {
return new B();
}
};
}

Factory.ServiceConsumer(A.serviceFactory); // A
Factory.ServiceConsumer(B.serviceFactory); // B

Java SE 8 的新特性:effectively final

However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final. - - - Java™ Documentation

1
2
3
4
5
6
7
8
9
class A {

void f(String s) {

// s 默认为 effectively final
// 当 s 的值改变后,s 就不再是 effectively final
s = "A";
}
}

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final. - - - Java™ Documentation

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
interface B {
void f();
}

class A {

// Java SE 8 之前的版本中,希望匿名内部类可以使用一个在其外部定义的对象
// 必须使用关键字 final,即 public B getB(final String s)
// 在 Java SE 8 中,引入了 effectively final 的概念
// 使得在这里可以省略 final
public B getB(String s) {

return new B() {

private String string = s;

@Override
public void f() {
System.out.println(s);
}
};
}
}

B b = new A().getB("B");
b.f(); // B

嵌套类

将内部类声明为 static,称之为嵌套类。嵌套类意味着:

  • 要创建嵌套类的对象,并不需要其外围类的对象
  • 不能从嵌套类的对象中访问非静态的外围类对象
  • 普通的内部类不能有 static 数据和 static 方法,而嵌套类可以

在接口内可以嵌套类,其中类默认为 publicstatic 的,甚至可以在类中实现其外围接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface A{

void f();

class B implements A{

@Override
public void f() {
System.out.println("B");
}
}
}

new A.B().f(); // B

可以使用嵌套类来测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {

public void f() {
System.out.println("A");
}

public static class Test {

public static void main(String[] args) {

A a = new A();
a.f(); // A
}
}
}

A.Test.main(null);

为什么需要内部类

使用内部类最吸引人的原因:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

另外内部类也有效的实现了多重继承:

  • 对于接口,可以选择使用单一类继承,或者使用内部类
  • 对于普通类或者抽象类,只能使用内部类
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
// 接口

interface A {
void fa();
}

interface B {
void fb();
}

// 单一类实现多重继承
class X implements A, B {

@Override
public void fa() {
System.out.println("XA");
}

@Override
public void fb() {
System.out.println("XB");
}
}

// 内部类实现多重继承
class Y implements A {

@Override
public void fa() {
System.out.println("YA");
}

B getB() {
return new B() {

@Override
public void fb() {
System.out.println("YB");
}
};
}
}

A a = new X();
a.fa(); // XA
B b = new X();
b.fb(); // XB

a = new Y();
a.fa(); // YA
b = new Y().getB();
b.fb(); // YB
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
// 类

class A {
void fa() {
}
}

abstract class B {
abstract void fb();
}

class X extends A {

@Override
void fa() {
System.out.println("XA");
}

B getB() {
return new B() {

@Override
void fb() {
System.out.println("XB");
}
};
}
}

A a = new X();
a.fa(); // XA
B b = new X().getB();
b.fb(); // XB

闭包与回调

闭包(closure) 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类就是一个闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private 成员。

回调(callback),对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。

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
interface A {
void f();
}

class B {
void f() {
}
}

// A 与 B 内都有 f() 方法,X 已经实现了 B 的 f() 方法
// 想要再实现 A 的 f() 方法,并且不影响到已经实现的 f() 方法
// 只能使用内部类来独立实现 f() 方法
class X extends B {

@Override
void f() {
System.out.println("XB");
}

private class Y implements A {

@Override
public void f() {
System.out.println("XA: " + this.toString());
}
}

A getCallbackReference() {
return new Y();
}
}

// 专门用来储存回调引用
class Caller {

private A callbackReference;

Caller(A callbackReference) {
this.callbackReference = callbackReference;
}

public A getCallbackReference() {
return callbackReference;
}
}

new X().f(); // XB
A a = new X().getCallbackReference();
a.f(); // XA: X$Y@1b6d3586

Caller callerA = new Caller(a);
callerA.getCallbackReference().f(); // XA: X$Y@1b6d3586

内部类与控制框架

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 基于时间执行控制
abstract class Event {

private long eventTime;
private long delayTime;

public Event(long delayTime) {
this.delayTime = delayTime;
start();
}

public void start() {
eventTime = System.nanoTime() + delayTime;
}

public boolean ready() {
return System.nanoTime() >= eventTime;
}

public abstract void action();
}

class Controller {

private List<Event> events = new ArrayList<Event>();

public void addEvent(Event e) {
events.add(e);
}

public void run() {

while (events.size() > 0) {

for (Event e : new ArrayList<Event>(events)) {
if (e.ready()) {
System.out.println(e.toString() + " : ");
e.action();
events.remove(e);
}
}
}
}
}

class GreenhouseControls extends Controller {

private boolean light = false;

// 使用内部类,在单一的类里面产生对同一个基类 Event 的多种导出版本
public class LightOn extends Event {

public LightOn(long delayTime) {
super(delayTime);
}

@Override
public void action() {
light = true;
System.out.println("Light is on");
}
}

public class LightOff extends Event {

public LightOff(long delayTime) {
super(delayTime);
}

@Override
public void action() {
light = false;
System.out.println("Light is off");
}
}
}

GreenhouseControls greenhouseControls = new GreenhouseControls();
greenhouseControls.addEvent(greenhouseControls.new LightOn((long) (2 * Math.pow(10, 9))));
greenhouseControls.addEvent(greenhouseControls.new LightOff((long) (4 * Math.pow(10, 9))));
greenhouseControls.run();

// 输出:
// GreenhouseControls$LightOn@1b6d3586 :
// Light is on
// GreenhouseControls$LightOff@4554617c :
// Light is off

内部类的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {
class B {
void f() {
System.out.println("B");
}
}
}

class X extends A.B {

// 必须在构造器内使用 enclosingClassReference.super()
public X(A a) {
a.super();
}
}

X x = new X(new A());
x.f(); // B

内部类的覆盖

当一个类继承了某个外围类的时候,内部类并不会发生什么变化,内部类是独立的实体,存在于自己的命名空间内。

局部内部类

局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及外围类的所有成员。

选择局部内部类,而非匿名内部类的主要原因有两个:

  • 需要一个已命名的构造器,或者需要重载构造器
  • 需要不止一个该内部类的对象
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
interface A {
void f();
}

class B {

private String s = "B";

A getA1() {

class X implements A {

@Override
public void f() {
System.out.println(s + " getA1");
}
}

return new X();
}

A getA2() {

return new A() {

@Override
public void f() {
System.out.println(s + " getA2");
}
};
}
}

B b = new B();
b.getA1().f(); // B getA1
b.getA2().f(); // B getA2

内部类标识符

主要以 $ 隔开,一般是 外围类标识符 + $ + 内部类标识符。如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。

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
interface A {
void f();
}

class B {

A getA1() {

class X implements A {

@Override
public void f() {
System.out.println(this.toString());
}
}

return new X();
}

A getA2() {

return new A() {

@Override
public void f() {
System.out.println(this.toString());
}
};
}

class C {

void f() {
System.out.println(this.toString());
}
}
}

B b = new B();
b.getA1().f(); // B$1X@1b6d3586
b.getA2().f(); // B$1@4554617c
b.new C().f(); // B$C@74a14482

// 局部内部类 B$1X
// 匿名内部类 B$1
// 内部类(包括嵌套类) B$C