文章目录
内部类
11.2链接外部类
当生成一个内部类的对象时,此对象与制造它的外部对象(enclosing object)之间就有了一种联系,所以它能访问其外部对象的所有成员,而不需要任何特殊条件。
11.3使用.this
和.new
如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟
.this
。这样产生的引用自动地具有正确的类型,这一点在编译期就知晓并受到检查,因此没有任何运行时开销。
// 如何使用.this
// Accessing the outer-class object
public class DotThis {
void f() {
System.out.println("DotThis.f()");
}
public class Inner {
public DotThis outer() {
return DotThis.this;
// A plain "this" would be Inner's "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
语法。
// Creating an inner class directly using .new syntax
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
要想直接创建内部类的对象,必须使用外部类的对象来创建。
11.4内部类与向上转型
在内部类中,可以使某个接口的实现能够完全不可见,并且不可用。所得到的的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。
// 示例的接口
public interface Destination {
String readLabel();
}
public interface Contents {
int value();
}
当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型。
class Parcel4 {
private class PContents implements Contents {
private int i = 11;
@Override
public int value() {
return i;
}
}
protected final class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
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();
Contents c = p.contents();
Destination d = p.destination("Tasmania");
// Illegal -- can't access private class
// Parcel4.PContents pc = p.new PContents();
}
}
private
内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给java编译器提供了生成高效代码的机会。
11.5内部类方法和作用域
在方法的作用域内创建一个完整的类。这被称作局部内部类:
// Nesting a class within a method
public class Parcel5 {
public Destination destination(String s) {
// 并不意味着destination方法执行完毕,PDestination就不可用了
final class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
}
在任意的作用域内嵌入一个内部类:
// Nesting a class within a scope
public class Parcel6 {
private void internalTracking(boolean b) {
// 并不是说TrackingSlip类的创建是有条件的,它其实与别的类一起编译过了
if (b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// Can't use it here! Out of scope
// TrackingSlip ts = new TrackingSlip("x");
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}
11.6匿名内部类
// Returning an instance of an anonymous inner class
public class Parcel7 {
public Contents contents() {
// 创建一个继承自Contents的匿名类的对象
return new Contents() { // Insert class definition
private int i = 11;
@Override
public int value() {
return i;
}
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
如果基类需要一个有参数的构造器:
public class Parcel8 {
public Wrapping wrapping(int x) {
// Base constructor call
return new Wrapping(x) {
@Override
public int value() {
return super.value() * 47;
}
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}
public class Wrapping {
private int i;
public Wrapping(int x) {
i = x;
}
public int value() {
return i;
}
}
在匿名类中定义字段时,还能够对其执行初始化操作:
public class Parcel9 {
// Argument must be final or "effectiviely final"
// to use within the anonymous inner class
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是
final
的。
为了在匿名内部类中实现类似于构造器的行为,可以通过实例初始化:
abstract class Base {
Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
// 在此例中,不要求变量一定是final的,因为被传递给匿名类的基类构造器,
// 它并不会在匿名类内部被直接使用
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备,也只能实现一个接口。
11.7嵌套类
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为
static
,这通常称为嵌套类。普通的内部类对象隐式保存了一个引用,指向创建它的外部类对象。
嵌套类意味着:
- 要创建嵌套类的对象,并不需要其外部类的对象。
- 不能从嵌套类的对象中访问非静态的外部类对象。
11.8为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口。
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了多重继承。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
11.8.1闭包与回调
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括
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();
}
}
interface Incrementable {
void increment();
}
// Very simple to just implement the interface
class Callee1 implements Incrementable {
private int i = 0;
@Override
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();
}
}
// If your class must implement increment() in some other way, you must use an inner class
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() {
// Specify outer-class method, otherwise you'll get an interface recursion
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {
callbackReference = cbh;
}
void go() {
callbackReference.increment();
}
}
11.9继承内部类
因为内部类的构造器必须连接到指向其外部类的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外部类对象的秘密的引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法。
public class InheritInner extends WithInner.Inner {
// InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
class WithInner {
class Inner {
}
}
11.10内部类可以被覆盖么
覆盖内部类就好像它是外部类的一个方法,其实并不起什么作用。
// An inner class cannot be overriden like a method
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
// 输出为:
// New Egg()
// Egg.Yolk()
当然,明确地继承某个内部类也是可以的:
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
// 先调用父类Egg2.Yolk的构造器
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
@Override
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
public BigEgg2() {
insertYolk(new Yolk());
}
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
}
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
Egg2() {
System.out.println("new Egg2()");
}
public void insertYolk(Yolk yy) {
y = yy;
}
public void g() {
y.f();
}
}