10.1 建立內部類
如果想從外部類的非靜態方法之外的任意位置建立某個內部類的對象,那麼必須具體地指定這個對象的類型:OuterClassName.InnerClassName.
10.2 連結到外部類
當產生一個內部類的對象時,此對象與製造它的外圍對象(enclosing object)之間就有了一種聯絡,所以它能訪問其外圍對象的所有成員,而不需要任何特殊條件。此外,內部類還擁有外圍類的所有元素的訪問權。
當某個外圍類的對象建立了一個內部類對象時,此內部類對象必定會秘密地捕獲一個指向那個外圍類對象的引用。然後,當你訪問此外圍類的成員時,就是用那個引用來選擇外圍類的成員。因此內部類對象只能在與其外圍類的對象相關聯情況下才能被建立(在內部類非static時)。構建內部類對象時,需要一個指向其外圍類對象的引用,如果編譯器訪問不了這個引用就會報錯。不過絕大多數時候這都無需程式員操心。
10.3 使用.this與.new
如果你需要產生對外部類對象的引用,可以使用外部類的名字後面緊跟原點和this.
如果你需要建立某個內部類對象,你必須在new運算式中提供對其他外部類對象的引用,這是需要使用.new文法。如下所示:
//: innerclasses/DotNew.java// Creating an inner class directly using the .new syntax.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內部類給類的設計者提供了一個途徑,通過這種方式可以完全阻止任何依賴於類型的代碼,並且完全隱藏了實現的細節。
//: innerclasses/TestParcel.javaclass Parcel4 { 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) { 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(); }} ///:~
10.5 在方法與範圍內的內部類
可以在一個方法裡面或者在任意的範圍內定義內部類。這麼做有兩個理由:
1)如前所述,你實現了某個類型的介面,於是可以建立並返回對其的引用。
2)你要解決一個複雜問題,想建立一個類來輔助你的解決方案,但是又不希望這個類是公用可用的。
若某個類在if語句的範圍內,這並不是說該類的建立是有條件的,它其實與別的類一起編譯過了。然後,在定義該類的範圍外,它是停用;除此以外,它與普通類一樣。
10.6 匿名內部類
在匿名類中定義欄位時,還能夠對它執行初始化操作。如果定義一個匿名內部類,並且希望它使用一個在其外部定義的對象,那麼編譯器會要求其參數引用是final的,如果你忘記了,會得到一個編譯時間錯誤。
匿名內部類不可能有命名構造器,但是通過執行個體初始化,就能夠達到為匿名內部類建立一個構造器的效果:
//: innerclasses/Parcel10.java// Using "instance initialization" to perform// construction on an anonymous inner class.public class Parcel10 { public Destination destination(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: { cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel10 p = new Parcel10(); Destination d = p.destination("Tasmania", 101.395F); }} /* Output:Over budget!*///:~
匿名內部類與正規類的繼承相比有些限制,因為匿名內部類既可以擴充類,也可以實現介面,但是不能兩者兼備。而且如果實現介面,也只能實現一個介面。
10.7 嵌套類
如果不需要匿名內部類與外圍類對象之間的聯絡,那麼可以將內部類申明為static。這通常稱為嵌套類。當內部類是static時:
1)要建立嵌套類的對象,並不需要其外圍類的對象;
2)不能從嵌套類的對象中訪問非靜態外圍類對象。
嵌套類與普通的內部類還有一個區別。普通內部類的欄位和方法,只能放在類的外部層次上,所以普通的內部類不能有static資料和static欄位,也不能包含嵌套類。但嵌套類可以包含所有這些東西。
10.7.1 介面內部的類
正常情況下,不能在介面內部放置任何代碼,但是嵌套類可以作為介面的一部分。你放到介面中的任何類都自動的是static和public的。
如果你想要建立某些公用代碼,使得他們可以被某個介面的所有不同實現所公用,那麼使用介面內部的嵌套類就會顯得方便。
//: 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.7.2 從多層嵌套類中訪問外部類的成員
//: innerclasses/MultiNestingAccess.java// Nested classes can access all members of all// levels of the classes they are nested within.class MNA { private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } }}public class MultiNestingAccess { 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(); }} ///:~
10.8 為什麼需要內部類
使用內部類最迷人的原因是:每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。
如果使用內部類,還可以獲得其他一些特性:
1)內部類可以有多個執行個體,每個執行個體都有自己的狀態資訊,並且與其外圍類對象的資訊相互獨立;
2)在單個外圍類,可以讓多個內部類以不同的方式實現同一個介面,或整合同一個類。
3)建立內部類對象的時刻並不依賴於外圍類對象的建立;
4)內部類並沒有令人迷惑的“si-a”關係,它就是一個獨立的個體。
10.9 內部類的繼承
問題在於,那個指向外圍類對象的“秘密的”引用必須被初始化。
//: innerclasses/InheritInner.java// Inheriting an inner class.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/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 局部內部類
可以在代碼塊裡面建立內部類,典型的方式是在一個方法體的裡面建立。局部內部類不能有訪問說明符,因為它表示外圍類的一部分,但是它可以訪問當前代碼塊內的常量,以及外圍類的所有成員。
10.12 內部類別識別項
由於每個類都會產生一個.class檔案。內部類產生的.class檔案的命名有嚴格的規則:外圍類的名字,加上“$”,再加上內部類的名字。