可以将一个类的定义放在另一个类的定义内部,这就是内部类。它允许把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。
创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体指出这个对象的类型: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
26class 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
24class 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
18class 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
12class 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
11class 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
21interface B {
void f();
}
class A {
private class AA implements B {
public void f() {
System.out.println("AA");
}
}
public B getB() {
return new AA();
}
}
B b = new A().getB();
b.f(); // AA
在方法和作用域内的内部类
可以在一个方法里面或者在任意的作用域内定义内部类,该内部类被称为 局部内部类
。这样做有两个理由:
- 实现某类型的接口,创建并返回对其的引用
- 创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的
1 | // 方法的作用域内 |
1 | // 在任意的作用域内 |
匿名内部类
1 | interface B { |
1 | // 存在有参数的构造器 |
1 | // 使用匿名内部类的工厂方法 |
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 | class A { |
An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final. - - - Java™ Documentation
1 | interface B { |
嵌套类
将内部类声明为 static
,称之为嵌套类。嵌套类意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象
- 不能从嵌套类的对象中访问非静态的外围类对象
- 普通的内部类不能有
static
数据和static
方法,而嵌套类可以
在接口内可以嵌套类,其中类默认为 public
和 static
的,甚至可以在类中实现其外围接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14interface A{
void f();
class B implements A{
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
17class 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 | // 接口 |
1 | // 类 |
闭包与回调
闭包(closure)
是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类就是一个闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private
成员。
回调(callback)
,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。
1 | interface A { |
内部类与控制框架
1 | // 基于时间执行控制 |
内部类的继承
1 | class A { |
内部类的覆盖
当一个类继承了某个外围类的时候,内部类并不会发生什么变化,内部类是独立的实体,存在于自己的命名空间内。
局部内部类
局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及外围类的所有成员。
选择局部内部类,而非匿名内部类的主要原因有两个:
- 需要一个已命名的构造器,或者需要重载构造器
- 需要不止一个该内部类的对象
1 | interface A { |
内部类标识符
主要以 $
隔开,一般是 外围类标识符 + $
+ 内部类标识符。如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。
1 | interface A { |