Java-内部类

一、创建内部类

把类的定义置于外围类里面:

public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;

public Destination(String label) {
this.label = label;
}
String readLabel() { return label; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}

public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tasmania");
}
}

如果需要从外部类的非晶态方法之外的任意位置创建内部类的对象,就需要指明这个对象的类型:OuterClassName.InnerClassName

二、链接到外部类

内部类似乎还只是一种名字隐藏和组织代码的模式。虽然有用,但还不是最引人注目的。当生成一个内部类对象时,此对象就与外部对象之间产生一种联系,因此他能访问其外围对象的所有成员,而不需要任何特殊条件。并且,内部类还拥有其外部类的所有元素的访问权。

public class Sequence {
interface Selector {
boolean end();
Object current();
void next();
}

private Object[] items;
private int next = 0;
public Sequence(int size) { items = new Object[size]; }
public void add(Object x) {
if (next < items.length)
items[next++] = x;
}
private class SequenceSelector implements Selector {
private int i = 0;
public boolean end() { return i == items.length; }
public Object current() { return items[i]; }
public void next() { if (i < items.length) i++; }
}
public Selector selector() {
return new SequenceSelector();
}

public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for (int i = 0; i < 10; i++) {
sequence.add(Integer.toString(i));
}
Selector selector = sequence.selector();
while (!selector.end()) {
System.out.println(selector.current() + " ");
selector.next();
}

}
}

这是一个迭代器设计模式的例子,使用外类包装数组,内部类包装迭代功能。可以看到SequenceSelector是提供Selector功能的private类。注意方法end、current、next都用到了objects,这是一个引用,他并不是SequenceSelect的一部分,而是外围类的一个private字段。
当某个外围类的对象创建一个内部类对象后,此内部类对象必定会秘密的捕获一个指向那个外围类对象的引用。这个过程编译器会帮助我们完成。

三、使用.this与.new

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟远点和this。这样产生的引用自动的具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。

public class DotThis {
void f() { System.out.println("DotThis.f()"); }
public class Inner {
public DotThis outer() { return DotThis.this; }
}
public Inner inner() { return new Inner(); }
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}

有时又需要告诉某些其他对象,去创建其某个内部类的对象。实现此目的,需要在new表达式中提供对其他外部类对象的引用,需要使用.new语法。

public class DotNew {
public class Inner {}

public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner in = dn.new Inner();
}
}

要想直接创建内部类的对象,不能去引用外部类的名字DotNew,而是必须使用外部类的对象来创建内部类对象。在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗链接到创建他的外部类对象上。但是如果是嵌套类(静态内部类),就不需要对外部类对象进行引用了。

四、内部类与向上转型

当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。

public class Parcel4 {
public interface Destination { String readLabel(); }
public interface Contents { int value(); }
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
public PDestination(String label) { this.label = label; }
public String readLabel() { return label; }
}
public Destination destination(String s) { return new PDestination(s); }
public Contents contents() { return new PContents(); }
}

public class TestParcel {

public static void main(String[] args) {
Parcel4 p = new Parcel4();
Parcel4.Contents c = p.contents();
Parcel4.Destination d = p.destination("Tasmania");
}
}

这里的内部类PContents必须依赖contents()方法才能初始化。通过这种方式能够完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。

五、在方法与作用域的内部类

前面讲述了内部类的典型用途,后面将会讲述内部类更难以理解的技术。例如:可以在一个方法里面或者在任意的作用域内定义内部类。理由如下:

  • 1.如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。
  • 2.要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
    在后面的例子中,实现如下:
  • 1.一个定义在方法中的类。
  • 2.一个定义在作用域内的类,此作用域在方法的内部。
  • 3.一个实现了接口的匿名类。
  • 4.一个匿名类,他扩展类有非默认构造器的类。
  • 5.一个匿名类,他执行字段初始化。
  • 6.一个匿名类,他通过实例初始化实现构建。

第一个例子展示了在方法的作用域内创建一个完整的类。被称为局部内部类。

public class Parcel5 {

public Parcel4.Destination destination(String s) {
class PDestination implements Parcel4.Destination {
private String label;
public PDestination(String label) { this.label = label; }
public String readLabel() { return label; }
}
return new PDestination(s);
}

public static void main(String[] args) {
Parcel5 p = new Parcel5();
Parcel4.Destination d = p.destination("Tasmania");
}
}

PDestination类是destination方法的一部分,而不是Parcel5的一部分。注意,出现在return语句中的向上转型。
当然在destination()中定义了内部类PDestination,并不意味着一旦dest()方法执行完毕,PDestination就不可用了。

下面例子展示了如何在任意的作用域内嵌入一个内部类。

public class Parcel6 {
private void internalTracking(boolean b) {
if (b) {
class TrackingSlip {
private String id;
public TrackingSlip(String id) { this.id = id; }
String getSlip() { return id; }
}
TrackingSlip t = new TrackingSlip("slip");
String s = t.getSlip();
}
// TrackingSlip...
}
public void track() { internalTracking(true); }

public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}

TrackingSlip类被嵌入在if语句的作用域内。并不是该类的创建是有条件,其实与别的类一起编译过了。然而在定义TrackingSlip的作用域外,是不可用的。

六、匿名内部类

看看下面的奇怪例子:

public class Parcel7 {
public Parcel4.Contents contents() {
return new Parcel4.Contents() {
private int i = 11;
public int value() { return i; }
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Parcel4.Contents c = p.contents();
}
}

contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起。另外这个类是匿名的,没有名字,并且还要创建一个对象。

创建一个继承自Contents匿名类的对象。通过new表达式返回的引用被自动向上转型为对Contents的引用:

public class Parcel7b {
class MyContents implements Parcel4.Contents {
private int i = 11;
public int value() { return i; }
}
public Parcel4.Contents contents() { return new MyContents(); }
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Parcel4.Contents c = p.contents();
}
}

在这个匿名中使用的默认构造器创建Contents。下面的代码则是在需要有参构造器时创建匿名内部类的做法:
public class Parcel8 {
public Wrapping wrapping(int x) {
return new Wrapping(x) {
public int value() { return super.value() * 47; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping wrapping = p.wrapping(10);
}
}
public class Wrapping {
private int i;
public Wrapping(int i) { this.i = i; }
public int value() { return i; }
}

可以看到Wrapping拥有一个要求传递一个参数的构造器。这就完成了对具有参数构造器的匿名内部类的配置。

在匿名类中定义字段时,还能够对其执行初始化操作:

public class Parcel9 {
public Parcel4.Destination destination(final String dest) {
return new Parcel4.Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}

public static void main(String[] args) {
Parcel9 p = new Parcel9();
Parcel4.Destination aabbcc = p.destination("aabbcc");
}
}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用为final。
因为匿名内部类不可能命名构造方法。因此通过实例初始化时添加有参的构造函数更是一个明智的方式。

看看下面的方式和构造器的区别:

public class AnonymousConstructor {

static abstract class Base {
public Base(int i) { System.out.println("base init"); }
public abstract void f();
}

static Base getBase(int i) {
return new Base(i) {
{ System.out.println("inside init"); }
public void f() { System.out.println("f()"); }
};
}

public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
out:
base init
inside init
f()

打印的日志能清除明白执行的顺序。

七、嵌套类

如果不需要内部类对象与外围类对象之间有联系,那么可以在内部类声明为staic。通常成为嵌套类。

  • 1.要创建嵌套类的对象,并不需要其外围类的对象。
  • 2.不能从嵌套类的对象中访问非静态的外围类对象。

嵌套类和普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。

public class Parcel11 {
private static class ParcelContents implements Parcel4.Contents {
private int i = 11;
public int value() { return i; }
}
protected static class ParcelDestination implements Parcel4.Destination {
private String label;
public ParcelDestination(String label) { this.label = label; }
public String readLabel() { return label; }
public static void f() {}
static int x = 10;
static class AnotherLevel {
public static void f() {}
static int x = 10;
}
}
public static Parcel4.Destination destination(String s) { return new ParcelDestination(s); }
public static Parcel4.Contents contents() { return new ParcelContents(); }

public static void main(String[] args) {
Parcel4.Contents c = contents();
Parcel4.Destination d = destination("Tasmania");
}
}

在main()中,没有任何Parcel11的对象是必需的,而是使用选取static成员的普通语法来调用方法。

接口内部的类

正常情况下,不能在接口内部放置代码,但是嵌套类可以作为接口的一部分。放到接口中任何的类都是public和static的。

public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {

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

public static void main(String[] args) {
new Test().howdy();
}
}
}

从多层嵌套类中访问外部类的成员

一个内部类被嵌套多少层并不重要,他能透明的访问所有他所嵌入的外围类的所有成员:

static class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}

public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}

可以看到在调用g()和f()方法不需要任何的条件。

八、为什么需要内部类

上面基本介绍完内部类的语法和语义。但是为什么需要这个功能?
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建他的外围类的对象。所以可以认为内部类提供了某种进入外围类的窗口。
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与变成问题九很难解决。从该角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了多重继承。也就是说,内部类允许继承多个非接口类型。

必须在一个类中以某种方法实现两个接口,这个问题有两个方案,使用单一类或内部类。
看下面的例子:

public class MultiInterface {
interface A {}
interface B {}
static class X implements A, B {}
static class Y implements A {
B makeB () { return new B() {}; }
}

static void takesA(A a) {}
static void takesB(B b) {}

public static void main(String[] args) {
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
}

这里假设在两种方式下的代码结构都确实有逻辑意义。遇到问题的时候,通常问题本身能够给出某些指引。如果没有任何其他限制,从实现观点看上面的例子并没有区别。
如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能够实现多重继承。
public class MultiImplementation {
static class D {}
static abstract class E {}
static class Z extends D {
E makeE() { return new E() {}; }
}

static void takesD(D d) {}
static void takesE(E e) {}

public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}

如果不需要解决多重继承问题,那么自然可以用别的方式编码。但是如果使用内部类,还可以获得以下特性:

  • 1.内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
  • 2.在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
  • 3.创建内部类对象的时刻并不依赖与外部类对象的创建。
  • 4.内部类并没有令人迷惑的is-a关系,他就是一个独立的实体。

闭包与回调

闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建他的作用域。内部类是面向对象的闭包,因为它不仅包含外围类对象的信息,还自动拥有一个窒息那个此外围类对象的引用。
通过内部类提供闭包的功能是优良的解决方案,他比指针更灵活,更安全。

public class Callbacks {

interface Incrementable { void increment(); }
static class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
System.out.println(i);
}
}

static class MyIncrement {
public void increment() { System.out.println("Other operation"); }
static void f(MyIncrement mi) { mi.increment(); }
}

static class Callee2 extends MyIncrement {
private int i = 0;
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
public void increment() {
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}

static class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) { callbackReference = cbh; }
void go() { callbackReference.increment(); }
}

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();
}
}

这个例子展示了外围类实现一个接口与内部类实现此接口之间的区别。针对类似指针的效果也有了明显的示例。

内部类与控制框架

在将要介绍的控制框架中,可以看到更多内部类例子。
控制框架是一类特殊的应用程序框架,他用来解决相应时间的需求。主要用来响应实现的系统被称为事件驱动系统。要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,他的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但是在本例中是指基于事件触发的

九、内部类的继承

十、内部类覆盖

十一、局部内部类

十二、内部类标识符