Java 1.1 引入內部類與匿名類機制,不僅是語法糖,更是物件導向設計在語言層面的深化。它們允許將邏輯相關的類封裝在一起,實現更細粒度的封裝、更自然的回調實現,以及更清晰的程式碼組織。善用這兩項特性,能讓代碼更具可讀性、可維護性與表達力。
簡單來說,內部類 是在另一個類(外部類)內部宣告的類。Java 語言規範賦予了極大的靈活性:
內部類可使用 public、protected、private 及套件預設存取修飾詞,語義與一般類別成員一致。其完整名稱遵循 外部類名.內部類名 格式(如 Outer.Inner),若在介面中則為 InterfaceName.InnerClass。使用 import 陳述式可簡化引用。
| 特性 | 靜態內部類 (static) | 非靜態內部類 |
|---|---|---|
| 外部實例關聯 | 無,獨立存在 | 持有外部類實例的隱含引用 |
| 存取外部成員 | 僅限 static 成員 | 可存取所有成員(含實例欄位/方法) |
| 實例化語法 | new Outer.StaticInner() | outerInstance.new Inner() |
| 宣告 static 成員 | 允許 | 僅允許常數欄位 (static final) |
以鏈結串列為例,Node 節點類僅供 LinkedList 內部使用,將其宣告為 private static class Node 既隱藏了實現細節,又避免了不必要的外部實例關聯:
public class LinkedList {
private static class Node { // 靜態內部類,不需外部實例
Object data;
Node next;
Node(Object data, Node next) { this.data = data; this.next = next; }
}
private Node head;
// ... 公開 API ...
}
匿名類是無名稱的類別宣告與實例化合而為一的語法結構:
new 介面或類別() {
// 類別主體:實現方法、覆寫方法、定義新欄位/方法
}
編譯器會為其生成類似 OuterClass$1.class 的類別檔案。本質上,匿名類屬於非靜態內部類,擁有相同的存取權限與限制(如需捕獲方法區域變數,變數須為 final 或實際上不可變)。
new Thread(() -> { ... }).start() 的前身。Comparator、Runnable 等函數式介面。button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按鈕被點擊:" + e.getActionCommand());
// 可直接存取外部類的 final/實際不可變區域變數
}
});
WeakReference,或確保及時移除監聽。Serializable 會連帶序列化外部實例,極易導致 NotSerializableException 或資料膨脹。建議:避免讓非靜態內部類實作序列化,或使用靜態內部類。{ ... }。Java 8 引入 Lambda 表達式與方法參考,本質上是函數式介面(單一抽象方法介面)的匿名類語法糖。例如:
// 匿名類寫法
Runnable r1 = new Runnable() { public void run() { System.out.println("Hi"); } };
// Lambda 寫法(更簡潔,無 this 引用歧義)
Runnable r2 = () -> System.out.println("Hi");
關鍵差異:Lambda 不建立新的 this 作用域,this 指向外部類實例,解決了匿名類中 this 指向混淆的問題。現代開發應優先使用 Lambda,僅在需實現多方法介面、需存取匿名類自身 this、或建構複雜邏輯時使用匿名/內部類。
內部類與匿名類是 Java 物件模型的重要延伸,它們將「封裝」的邊界從類別級別細化到邏輯單元級別。理解靜態與非靜態的語義差異、掌握作用域與生命週期的關聯、避開序列化與記憶體洩漏陷阱,是進階 Java 開發者的必修課。在 Lambda 普及的今天,深入理解其底層機制,更能讓我們在架構設計與除錯時遊刃有餘。