內部類是一種非常有用的特性,因為它允許你把一些邏輯相關的類組織在一起,并控制位于內部的類的可視性。
如果想從外部類的非靜態方法之外的任意位置創建某個內部類的對象,那么必須具體地指明這個對象的類型,OutClassName.InnerClassName
內部類自動擁有對其外部類所有成員的訪問權。
內部類的對象只能在與其外圍類的對象相關聯的情況下才能被創建,構建內部類對象時,需要一個指向其外圍類對象的引用,如果編譯器訪問不到這個引用就會報錯。
10.3 使用.this與.new
如果你需要生成對外部類對象的引用,可以使用外部類的名字后面緊跟.this,這樣產生的引用自動地具有正確的類型,這一點在編譯期就被知曉并受到檢查,因此沒有任何運行時開銷。
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();
}
} /* Output:
DotThis.f()
*///:~
創建某個內部類的對象,需要使用.new語法,必須使用外部類的對象來創建該內部類對象。
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
} ///:~
10.4 內部類與向上轉型
內部類——某個接口的實現——能夠完全不可見,并且不可用。所得到的只是指向基類或接口的引用,所以能夠很方便地隱藏實現細節。
private內部類給類的設計者提供了一種途徑,通過這種方式可以完全阻止任何依賴于類型的編碼,并且完全隱藏了實現的細節。此外,從客戶端程序員的角度來看,由于不能訪問任何新增加的、原本不屬于公共接口的方法,所以擴展接口是沒有價值的。
10.5 在方法和作用域內的內部類
可以在一個方法里或者在任意的作用域內定義內部類,這么做有兩個理由:
- 你實現了某類型的接口,于是可以創建并返回對其的引用。
- 要解決一個復雜的問題,想創建一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。
//: innerclasses/Parcel5.java
// Nesting a class within a method.
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
} ///:~
在方法的作用域內(而不是在其他類的作用域內)創建一個完整的類,這被稱為局部內部類。在destionation方法之外不能訪問PDestination,在return語句中又向上轉型成基類。
內部類可以在任意的作用域內嵌入,并不是說該類的創建是有條件的,它與其他別的類一起編譯過了,然而在其作用域之外他是不可用的。
10.6 匿名內部類
匿名內部類指的是創建一個繼承自某個基類的匿名類的對象。
在匿名類末尾的分號,并不是用來標記此內部類結束的。它標記的是表達式的結束,只不過這個表達式正巧包含了匿名內部類罷了。
如果匿名內部類使用一個在外部定義的對象,那么編譯器會要求其參數引用是final的。
匿名類中不可能有命名構造器(因為它根本沒有名字),但通過實例初始化,就能夠達到為匿名內部類創建一個構造器的效果。
//: innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import static net.mindview.util.Print.*;
abstract class Base {
public Base(int i) {
print("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{ print("Inside instance initializer"); }
public void f() {
print("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*///:~
這里的變量i不是final的,因為i被傳遞給匿名類的基類的構造器,它并不會在匿名類內部被直接使用。
匿名內部類與正規的繼承相比有些受限,因為匿名內部類既可以擴展類,也可以實現接口,但是不能兩者兼備。而且如果是實現接口,也只能實現一個接口。
利用匿名內部類可以更好地創建工廠方法:
//: innerclasses/Factories.java
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
public static ServiceFactory factory =
new ServiceFactory() {
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service {
private Implementation2() {}
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
public static ServiceFactory factory =
new ServiceFactory() {
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(Implementation1.factory);
// Implementations are completely interchangeable:
serviceConsumer(Implementation2.factory);
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~
優先使用類而不是接口,如果你的設計中需要某個接口,你必須了解它。否則,不到迫不得已,不要將其放到你的設計中。
10.7 嵌套類
如果不需要內部類對象與其外圍類對象之間有聯系,那么可以將內部類聲明為static,這通常稱為嵌套類。
普通的內部類對象隱式地保存了一個引用,指向創建它的外圍類對象。但是,當內部類是static的,嵌套類意味著:
- 要創建嵌套類的對象,并不需要其外圍類的對象。
- 不能從嵌套類的對象中訪問非靜態的外圍類對象。
正常情況下不能在接口內部放置任何代碼,但嵌套類可以作為接口的一部分。因為類是static的,只是將嵌套類置于接口的命名空間內,這并不違反接口的原則,甚至可以在內部類實現其外圍接口。
//: innerclasses/ClassInInterface.java
// {main: ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
} /* Output:
Howdy!
*///:~
一個內部類被嵌套多少層并不重要——它能透明地訪問所有它所嵌入的外圍類的所有成員。
10.8 為什么需要內部類
每個內部類都能獨立地繼承自一個接口的實現,所以無論外圍類是否已經繼承了某個接口的實現,對于內部類都沒有影響。內部類使得多重繼承的解決方案變得完整。
使用內部類獲得其他一些特性:
- 內部類可以有多個實例,每個實例都有自己的狀態信息,并且與其外圍類對象的信息相互獨立。
- 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類。
- 創建內部類對象的時刻并不依賴于外圍類對象的創建。
- 內部類并沒有令人迷惑的is-a關系,它就是一個獨立的實體。
上面的地三條,這里的重音在時刻兩個字,作者想突出內部類是外部類“輕量級的可選組件”這個特性。其實也很好理解,比如迭代器作為很多容器的外部類,并不是在創建容器的時候就被一起創建的。而是要我們再需要它的時候,手動創建實例,日工容器內部元素的視圖。突出“optional”的特性,而不是說它本身和外部類容器沒關系。
//: innerclasses/Callbacks.java
// Using inner classes for callbacks
package innerclasses;
import static net.mindview.util.Print.*;
interface Incrementable {
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
print(i);
}
}
class MyIncrement {
public void increment() { print("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();
i++;
print(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();
}
} /* Output:
Other operation
1
1
2
Other operation
2
Other operation
3
*///:~
此實例展示了回調,和當繼承父類重寫父類方法與需要繼承的接口中的方法重疊時,用內部類解決的思路。
內部類在實現控制框架上,可以在總控制類內部實現具體操作的內部類,能夠很好地繼承事件抽象類,而且能夠方便訪問外部類的所有方法和實例。
10.9 內部類的繼承
class WithInner {
class Inner {}
}
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);
}
} ///:~
注意構造器的實現。
10.10 內部類可以被覆蓋嗎
//: innerclasses/BigEgg.java
// An inner class cannot be overriden like a method.
import static net.mindview.util.Print.*;
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() { print("Egg.Yolk()"); }
}
public Egg() {
print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() { print("BigEgg.Yolk()"); }
}
public static void main(String[] args) {
new BigEgg();
}
} /* Output:
New Egg()
Egg.Yolk()
*///:~
當繼承了某個外圍類的時候,內部類并沒有發生什么特別神奇的變化。這兩個內部類是完全獨立的兩個實體,各自在自己的命名空間內。
//: innerclasses/BigEgg2.java
// Proper inheritance of an inner class.
import static net.mindview.util.Print.*;
class Egg2 {
protected class Yolk {
public Yolk() { print("Egg2.Yolk()"); }
public void f() { print("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { print("New Egg2()"); }
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() { print("BigEgg2.Yolk()"); }
public void f() { print("BigEgg2.Yolk.f()"); }
}
public BigEgg2() { insertYolk(new Yolk()); }
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
} /* Output:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
*///:~
10.11 局部內部類
局部內部類不能有訪問說明符,因為它不是外圍類的一部分;但是它可以訪問當前代碼塊內的常量,以及此外圍類的所有成員。
既然局部內部類的名字在方法外是不可見的,那為什么我們仍然使用局部內部類而不是匿名內部類呢?唯一的理由是,我們需要一個已命名的構造器,或者需要重載構造器,而匿名內部類只能用于實例初始化。另一個理由就是需要不止一個該內部類的對象。
內部類標識符
內部類生成的.class文件以包含它們的Class對象信息。外圍類的名字,加上“$”,再加上內部類的名字。匿名內部類會產生一個數字作為其表示符。