可以将一个类的定义放到另一个类的定义内部,这就是内部类。
10.1 创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体指明这个对象的类型:OuterClassname.InnerClassName
10.2 链接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种关系,所以他能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
当某个外围类的对象创建一个内部类对象时,次内部类对象必定会秘密地捕获一个指向那个外围类对象的引用(内部类是非static类时)。
10.3 使用.this 和 .new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this:OuterClassName.this
要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字,而是必须使用外部类的对象来创建该内部类的对象。
在拥有外部类对象之前是不可能创建内部类对象的。
一个内部类被嵌套多少层并不重要,它能透明地访问所有它所嵌入的外围类的所有成员。
10.4 内部类和向上转型
当内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类有了特殊的作用。内部类关于某个接口的实现能够完全不可见,并且不可用。我们只能得到指向基类或者接口的引用,所以能够很方便地隐藏实现细节。
10.5 在方法和作用域内的内部类
又名局部内部类。
使用场景:
- 你实现了某类型的接口,于是可以创建并返回对其的引用
- 你要解决一个复杂的问题,想创建一个雷来辅助你的解决方案,但是又不希望这个雷士公共可用的。
10.6 匿名内部类
看起来似乎是你正要返回一个对象。但是然后(在到达预付结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义”。即:定义并立即返回一个类的实例。
- 可以使用实例初始化达到匿名内部类的构造器的效果。但是不能重载实力初始化方法,所以你仅有一个这样的构造器。
- 匿名内部类既可以扩展类,又可以实现接口,但是不能两者兼备。而且实现接口,也只能实现一个接口。
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器要求其参数引用是final的。
10.7 嵌套类 static
- 要创建嵌套类的对象,并不需要其外围类的对象
- 不能从嵌套类的对象中访问非静态的外围类的对象。
10.7.1 接口内部的类
如果你想要创建某些公用代码,使得他们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类会显得很方便。
10.8 为什么需要内部类
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。
如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性:
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
- 创建内部类对象的时刻并不依赖于外围类对象的创建。
- 内部类没有令人迷糊的"is-a"关系,他就是一个独立地实体。
10.8.1闭包与回调
内部类是面向对象的闭包。
interface Incrementable{
void increment();
}
class MyIncrement{
public void increment(){
System.out.println("Other operation");
}
public static void f(MyIncrement mi){
mi.increment();
}
}
class Callee1 implements Incrementable{
private int i=0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
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() {
Callee2.this.increment();
System.out.println("Closure increment");
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackRefrence;
Caller(Incrementable cbh){
callbackRefrence=cbh;
}
void go(){
callbackRefrence.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();
}
}
Callee1
是简单地解决方式.Callee2
继承自MyIncrement,已经有一个不同的increment()
方法,与Incrementable冲突且完全不想关。所以不能为了Incrementable
的用途而覆盖inrement()方法,于是只能使用内部类独立地实现Inrementable。
在Callee2中除了
getCallbackReference()
以外,其他成员都是private的。只能通过interfaceIncrementable
接口。这是一个安全的钩子(hook),无论谁获得Incrementable
的引用,都只能调用increment()
方法。
10.8.2 内部类与控制框架
在控制框架中使用内部类 ,可以:
- 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装起来。内部类用来表示解决问题所需的各种不同的action()。
- 内部类能够很容易地访问外围类的任意成员,避免实现变得笨拙。
10.9 内部类的继承
语法:
enclosingClassReference.super();
class WithInner{
class Inner{}
}
public class InheritInner extends WithInner.Inner{
public InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args){
WithInner inner=new WithInner();
InheritInner ii=new InheritInner(inner);
}
}
10.10 内部类可以被覆盖吗?
当继承了某个外围类的时候,内部类并没有发生什么神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。
10.11 局部内部类
可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为他不是外围类的一部分;但是他可以访问当前代码块内的常量,以及此外围类的所有成员。
使用局部内部类而不是匿名内部类的理由:
- 我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实力初始化。
- 需要不止一个该内部类的对象。
10.12 内部类标识符
内部类类文件的命名有严格的规则:外围类的名字,加上"$",再加上内部类的名字。
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。