第十章 内部类
可以将一个类的定义放在另一个类的定义内部。
内部类和组合的概念完全不同。内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,内部类远不止如此,它了解外围类,并能与之通信。
1 创建内部类
内部类的对象可以看作是和外部类的对象相联的。没有外部类的对象,无法创建内部类的对象。
2 连接到外部类
Java非static的普通内部类自动拥有对其外围类所有成员的访问权。
Java普通内部类能访问其外围对象(enclosing object)的所有成员,而不需要任何特殊条件 。
在Java中,当某个类创建一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类的对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。这些细节是由编译器处理的。Java的迭代器复用了这个特性。
内部类的对象只能在与其外围类的对象相关联的情况下才能被创建,构建内部类对象时,需要一个指向其外围类对象的引用。
3 .this和.new
Java非static的普通内部类可应用.this
返回其外围对象的引用。
外围对象可应用.new
来生成一个内部类对象。
Outer outer = new Outer();
Outer.Inner inner=outer.new Inner();
如果创建的是嵌套类(静态内部类),就不需要外部类对象的引用。
4 内部类和向上转型
内部类的构造函数和内部类具有相同的访问属性。
外部类可以访问内部类的private域和方法。内部类和外部类的访问权限是双向的。
5 在方法和作用域内使用内部类
可以在一个方法里或者在任意的作用域内定义内部类。这么做有两个理由:
a.实现了某个接口,于是可以创建并返回对其的引用。
b.要解决一个复杂的问题,想创建一个类来辅助,但又不希望这个类是公共可用的。
匿名类不可能有构造函数。
6 匿名内部类
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。
由于匿名内部类没有构造函数,可以用语句块的形式对类的对象进行初始化。
回看工厂类:
代码变得更加简洁,明了。
7 嵌套类
内部类声明为static
时,不再包含外围对象的引用.this
,称为嵌套类(与C++嵌套类大致相似,只不过在C++中那些类不能访问私有成员,而在Java中可以访问)。
静态的内部类也被称为嵌套类,它与普通内部的区别:
a.要创建嵌套类的对象,并不需要外围类的对象。
b.不能从嵌套类的对象中访问非静态的外围类数据。
c.嵌套类可以有static方法和域,还可以有嵌套类,普通内部类不可以。
嵌套类可以作为接口的一部分(正常情况下,接口内部不能放置任何代码)。放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。
甚至可以在嵌套类中实现其外围接口。如果想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共有,那么使用接口内部的嵌套类会很方便。
一个内部类被嵌套多少层并不重要——它能透明地访问它所嵌入的外围类的所有成员。
如果在外围类中使用嵌套类,直接使用类名就可以,但是如果在其他地方使用嵌套类,则必须使用“外围类.嵌套类”的形式。
书中建议在每个类中都写一个主函数用来测试这个类。这样有一个缺点,是必须带有这些额外的测试代码。可以使用嵌套类来放置主函数和测试代码。接口中的嵌套类是不会继承到它的实现中的。
8 为什么要使用内部类
内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
内部类实现一个接口与外围类实现这个接口的区别是后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:
每个内部类能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类使得Java实现继承多个非接口类型(类或抽象类)。
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互对立。
在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
内部类对象创建的时间点不与外部类对象的创建绑定在一起。
内部类并没有令人迷惑的“is-a”关系,它是一个独立的实体。
9 闭包和回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用(.this),在此作用域内,内部类有权操作所有的成员,包括private成员。
回调(callback),通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。Java中没有指针,通过内部类提供的闭包功能可以实现回调。
回调的价值在于它的灵活性——可以在运行时动态的决定需要调用什么方法。在实现GUI功能的时候,到处都用到了回调。
这里有个例子:
// Callbacks.java
// using inner classes for callbacks
interface Incrementable{
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable{
private int i = 0;
public void increment(){
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;
public void increment(){
super.increment();
System.out.println(++i);
}
private class Closure implements Incrementable{
public void increment(){
// Specify outer-class method, otherwise you'd 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();
}
}
/**Uoutput:
* Other operation
* 1
* 1
* 2
* Other operation
* 2
* Other operation
* 3
**/
10 内部类的继承
默认的构造方法不会编译通过
构造函数必须有一个外部类的引用参数
class WithInner {
class Inner {
}
}
public class InheritInner extends WithInner.Inner {
public InheritInner(WithInner wi){
wi.super(); //这个很重要
}
}
11 内部类可否被覆盖
当继承了某个外围类的时候,内部类并没有发生什么变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。
12 局部内部类
方法或作用域内的内部类(局部内部类)可以访问当前代码块内的常量以及外围类的所有变量。
既然局部内部类的名字在作用域外是不可见的,那为什么不适用匿名内部类?有两个原因:
a.需要构造函数。
b.需要多个该内部类的实例。
13 内部类标识符
每个类会产生一个.class文件,其中包含了如何创建该类型对象的全部信息(此信息产生一个meta-class,叫做Class对象)。内部类的命名规则:外围类的名字加上$,再加上内部类的名字。如果内部类是匿名的,编译器会简单的产生一个数字作为其标识符。
Java的接口和内部类比其他面向对象的概念更深奥复杂,还需要反反复复多看几遍。