
本文共 8005 字,大约阅读时间需要 26 分钟。
一、什么是内部类
内部类( inner class) 是定义在另一个类中的类。
二、内部类
1.内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
2.内部类可以对同一个包中的其他类隐藏起来。
3.当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较
便捷。
4.内部类可以用来解决多重继承问题。
5.内部类中的静态域必须是final修饰的。
三、创建内部类
public class Parcel { /** 内部类Contents*/ class Contents { private int i = 11; public int value() { return i; } } /** 内部类Destination*/ class Destination { private String label; Destination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } /** 使用此方法实例化内部类Destination*/ public Destination to(String s) { return new Destination(s); } /** 使用contents方法实例化内部类Contents*/ public Contents contents() { return new Contents(); } /** 在Parcel中使用内部类 */ public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(c.value()); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel parcel = new Parcel(); parcel.ship("Tasmania"); /** 在外部创建内部类*/ Parcel p = new Parcel(); Parcel.Contents contents = p.contents(); Parcel.Destination destination = p.to("Borneo"); }}
当我们在ship()方法中使用内部类的时候,与使用普通方法没什么不同。在这里,实际的区别只是内部类是定义在Parcel中。但这并不是内部类与普通类的唯一区别。
更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用,就像在to()和contents()方法中看到的一样。
如果想要从外部的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main方法中那样,具体地指明这个对象的类型:OutClassName.InnerClassName。
四、链接到外部内
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以他能访问其外围对象的所有成员,而不需要任何特殊条件。此外内部类还拥有其外围内的所有元素的访问权。示例如下:
public interface Selector { boolean end(); Object current(); void next();}
public class Sequence { 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.print(selector.current() + ", "); selector.next(); } }}
五、使用.this和.new
如果你需要在内部类中生成对外部类对象的引用,可以使用外部类的名字后面紧跟点this(
InnerClassName.this
)。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。示例如下:
public class DotThis { class Inner{ DotThis getOuter(){ return DotThis.this; } }}
有时候你可能想要告知某些其他对象,去创建某个内部类的对象。要实现此目的,你就必须在new表达式中提供对其他外部类对象的引用,这需要使用.new语法。示例如下:
public class DotNew { class Inner{ } public static void main(String[] args) { DotNew.Inner inner = new DotNew().new Inner(); }}
六、内部类向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。这是应为此内部类–某个接口的实现–能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。示例如下:
public interface Contents { int value();}
public interface Destination { String readLabel();}
public class Parce14 { private class PContents implements Contents { private int i = 11; public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } 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) { Parce14 p = new Parce14(); Contents c= p.contents(); Destination d = p.destination("Tasmania"); }}
Parcel4中增加了一些新东西:内部类PContents是private,所以除了ParceM,没有人能访问它。PDestination是protected,所以只有Parcel4及其子类、还有与Parcel4同一个包中的类 (因为protected也给予了包访问权)能访问PDestinatkm,其他类都不能访问PDestination。这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下 转型成private内部类(或protected内部类,除非是继承自它的子类),因为不能访问其名字,就像在TestParcel类中看到的那样。干是,private内部类给类的设计者提供了一种途径,通过这种 方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序 员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没 有价值的。这也给Java编译器提供了生成更高效代码的机会。
七、在方法和作用域中的内部类(局部内部类)
public interface Inner{ }
public class Outer { public Inner contents() { class Realize implements Inner { } return new Realize(); }}
八、匿名内部类
public abstract class Inner { public Inner(int i) { }}
public class Outer { public Inner contents(int i) { return new Inner(i) { }; }}
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。(JDK1.8开始则不需要)
九、嵌套类
如果不需要内部类对象与其外围类之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。嵌套类意味着:
1)要创建嵌套类的对象,并不需要其外围类的对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。
public class Outer { /** 嵌套类 */ public static class Inner { } public static void main(String[] args) { Outer.Inner inner = new Outer.Inner(); }}
- 接口内部的类
嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是public和static的。你甚至可以在内部类中实现其外围接口。就像下面这样:
public interface Outer { void out(); /** 接口中的嵌套类 */ class Inner implements Outer{ @Override public void out() { System.out.println("Inner"); } public static void main(String[] args) { new Inner().out(); } }}
- 从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要–它能透明地访问所有它所嵌入的外围类的所有成员。
public class Man { private void f(){ } class A{ // 第一个内部类 private void g(){ } public class B{ //第二个内部类 void h(){ f(); g(); } } }}
public class Outer { public static void main(String[] args) { Man.A.B b = new Man().new A().new B(); b.h(); }}
十、闭包与回调
闭包(closure)是一个可以调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。
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(); // Other operation //1 //1 //2 //Other operation //2 //Other operation //3 } }/*----------------*/interface Incrementable{ void increment();}class Callee1 implements Incrementable{ private int i = 0; public void increment() { i++; System.out.println(i); }}class MyIncrement{ public void increment(){ System.out.println("Other operation");} static void f(MyIncrement mi){ mi.increment(); }}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(); }}class Caller { private Incrementable callbackReference; Caller(Incrementable cbh) { callbackReference = cbh; } void go() { callbackReference.increment(); }}
这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区別。就代码而言, CaHeel是简单的解决方式。CaMee2继承自Mylncrement,后者已经有了一个不同的increment()
方法,并且与Incrnnentable接口期望的incrementO方法完全不相关。所以如果Callee2继承了 Mylncrement,就不能为了Incrementable的用途而覆盖incrementO方法,于是只能使用内部类 独立地实现IncremeiUable。还要注意,当创建了一个内部类时,并没有在外围类的接口中添加 东西,也没有修改外围类的接口。
注意,在Callee2中除了getCallbackReferenceO以外,其他成员都是private的。要想建立与 外部世界的任何连接,interface Incrementable都是必需的。在这里可以看到,interface是如何 允许接口与接口的实现完全独立的。
内部类Closure实现了Incrementable,以提供一个返回CaHee2的“钩子” (hook)—而且 是一个安全的钩子。无论谁获得此Incrementable的引用,都只能调用incrementO,除此之外没 有其他功能(不像指针那样,允许你做很多事愤)。
Callei•的构造器需要一个Incrementable的引用作为参数(虽然可以在任意时刻捕获回调引 用),然后在以后的某个时刻,Callei•对象可以使用此引用回调CaMee类。
回调的价值在于它的灵活性一可以在运行时动态地决定需要调用什么方法。这样做的好 处在第22章可以看得更明显,在那里实现GUI功能的时候,到处都用到了回调。
发表评论
最新留言
关于作者
