提到 Java 內部類,許多開發者可能感到陌生。其實類似概念在 C++ 中以「嵌套類」形式存在。乍看之下,內部類似乎多餘,尤其對初學者而言用處不明顯。但深入研究後會發現,Java 設計者在內部類上確實用心良苦。掌握內部類是進階 Java 程式設計的關鍵一環,能讓程式結構更優雅。
public interface Contents { int value(); }
public interface Destination { String readLabel(); }
public class Goods {
private class Content implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class GDestination implements Destination {
private String label;
private GDestination(String whereTo) { label = whereTo; }
public String readLabel() { return label; }
}
public Destination dest(String s) { return new GDestination(s); }
public Contents cont() { return new Content(); }
}
class TestGoods {
public static void main(String[] args) {
Goods p = new Goods();
Contents c = p.cont();
Destination d = p.dest("Beijing");
}
}
在這個例子中,Content 和 GDestination 定義在 Goods 內部,分別用 private 和 protected 控制存取層級。它們實現了介面 Contents 和 Destination。在 main 方法中,直接透過介面型別操作,甚至看不見內部類的名字!這就是內部類的第一大優勢——隱藏實作細節,強化封裝性。
同時,我們也發現了在外部類作用域外取得內部類物件的第一種方法:利用外部類方法建立並回傳。例子中的 cont() 和 dest() 就是這樣做的。還有其他方法嗎?當然有,語法如下:
OuterClass outerObject = new OuterClass(Constructor Parameters); OuterClass.InnerClass innerObject = outerObject.new InnerClass(Constructor Parameters);
注意:建立非靜態內部類物件時,必須先建立對應的外部類物件。原因引出下一個主題——
修改前例,給 Goods 增加 private int valueRate = 2;,內部類 Content 的 value() 計算時乘上此係數。我們發現 value() 可以存取 valueRate,這是內部類的第二大優勢——內部類物件能存取建立它的外部類物件的所有成員,包含私有變數。這極大豐富了設計思路。
為實現此功能,Java 編譯器在建立內部類物件時,隱式傳入並保存外部類物件的引用。這也是為什麼在外部類作用域外建立內部類物件,必須先建立外部類物件的原因。
若內部類成員變數與外部類同名(遮蔽),可用 OuterClass.this 表示外部類引用,輕鬆解決遮蔽問題。
內部類也可以是靜態的。與非靜態內部類最大區別在於:靜態內部類沒有指向外部類的引用。這點上它類似 C++ 的嵌套類。Java 內部類與 C++ 嵌套類最大不同,就在於是否持有外部引用。
此外,非靜態內部類中不能有靜態資料、靜態方法或靜態內部類;但靜態內部類卻可以擁有這一切。這是兩者的第二個區別。
Java 內部類也可以是局部的,定義在方法甚至程式碼區塊內。例如在方法 dest 中定義內部類,最後由方法回傳該內部類物件。若僅需建立一個物件並回傳,這樣做非常適合。當然,定義在方法中的內部類用途遠不止於此。
public class Goods2 {
private void internalTracking(boolean b) {
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();
}
}
// ...
}
你不能在 if 區塊外建立此內部類物件,因為超出作用域。但在編譯時,TrackingSlip 和其他類一樣被編譯,只是有自己的作用域,超出則無效。
匿名內部類語法看似古怪,但如同匿名陣列,當你只需建立一個類的物件且不需要其名字時,使用它能讓程式碼簡潔清楚。語法規則:
new InterfaceName() { ... };
// 或
new SuperClassName() { ... };
例子:
public class Goods3 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
};
}
}
方法 cont() 直接用匿名內部類回傳實現 Contents 介面的物件,確實簡潔。
在 Java 事件處理中,匿名內部類被大量使用。例如關閉視窗:
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
注意事項:匿名內部類沒有名字,所以沒有建構子(若繼承的父類只有帶參數建構子,建立時必須帶上參數並用 super 調用)。初始化成員變數有三種方法:
final)首先,若你想實現一個介面,但介面中的某方法與你構想的類中某方法同名同參數,此時可建一個內部類實現該介面。因為內部類能存取外部類所有內容,所以能完成所有直接實現介面的功能。
但你可能質疑:更改方法名不就行了?確實,以此作為設計內部類的理由,說服力不足。
真正原因是: Java 內部類配合介面,能解決 C++ 程式設計師常抱怨的 Java 問題——沒有多重繼承。C++ 多重繼承設計複雜,而 Java 透過內部類加介面,能很好實現多重繼承的效果。
本文旨在介紹內部類概念與用法,後續將舉更多具體例子,並介紹如何用內部類建構 Application Framework。