內部類是 Java 語言中極具特色的機制,它允許在一個類別內部定義另一個類別,從而實現邏輯封裝、狀態共享與回調設計。然而,靜態內部類、成員內部類、區域內部類、匿名內部類之間的差異、訪問權限規則、外部類引用持有機制,往往讓開發者困惑。本文通過代碼實證,系統梳理核心知識點,助你徹底搞懂內部類。
| 類型 | 定義位置 | 可用修飾符 | 隱含外部類引用 | 典型用途 |
|---|---|---|---|---|
| 靜態內部類 | 類別成員位置 | 所有類別修飾符 | 無 | 工具類、單例、Builder 模式 |
| 成員內部類 | 類別成員位置 | 所有類別修飾符 | 有 | 迭代器、視圖類、狀態綁定 |
| 區域內部類 | 方法/程式碼塊內 | 無(隱含 final) | 有 | 複雜邏輯封裝、臨時類型 |
| 匿名內部類 | 表達式中 | 無 | 有 | 事件監聽、執行緒、函數式介面實作 |
public class Outer {
private int outerNum = 1;
// 非靜態內部類(成員內部類)
class Inner {
// 編譯錯誤:非靜態內部類不能宣告 static 成員
// private static int innerStatic = 2;
private int innerNum = 2; // 非靜態成員 OK
public Inner() {
innerNum += outerNum; // 可直接訪問外部類實例成員
System.out.println("Inner: " + innerNum);
}
}
// 靜態內部類
static class StaticInner {
private static int staticMember = 10; // 靜態成員 OK
private int instanceMember = 20; // 非靜態成員 OK
public StaticInner() {
// 僅能訪問外部類靜態成員
System.out.println("StaticInner: " + staticMember);
}
}
}
原因: 非靜態內部類實例隱含持有外部類實例引用,屬於外部實例一部分;靜態內部類獨立存在,不依賴外部實例。
public class Outer {
private static String staticField = "Static";
private String instanceField = "Instance";
static class StaticInner {
private int innerNum = 1;
public void access() {
// OK:訪問外部類靜態成員
System.out.println(staticField);
// 編譯錯誤:無法訪問外部類非靜態成員
// System.out.println(instanceField);
// OK:訪問自身非靜態成員
System.out.println(innerNum);
}
}
}
public class Outer {
private static String staticField = "Static";
private String instanceField = "Instance";
class Inner {
public void accessAll() {
System.out.println(staticField); // OK
System.out.println(instanceField); // OK
}
}
}
編譯器會為非靜態內部類隱式添加一個指向外部類實例的引用欄位(Outer$Inner.this$0)。創建內部類實例時,必須依附於外部類實例:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 必須透過外部實例創建
Outer.StaticInner si = new Outer.StaticInner(); // 無需外部實例
若長壽命物件(如單例、靜態集合)持有非靜態內部類實例,將間接持有外部類實例,導致外部類無法被 GC 回收。解決方案: 優先使用靜態內部類,或將內部類提取為獨立頂層類別。
匿名內部類是實作單一介面(函數式介面)的簡寫形式,Java 8+ 可進一步簡化為 Lambda:
// 匿名內部類
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Clicked");
}
});
// Lambda (Java 8+)
button.addActionListener(e -> System.out.println("Clicked"));
注意: 匿名內部類可存取外部方法的 final 或「實際上 final」區域變數;Lambda 同樣受此限制。
static,避免隱性引用與記憶體洩漏。StringBuilder、HttpClient.Builder,構建器與產品類邏輯聚合且無需外部實例。private static 方法。NotSerializableException 或資料膨脹。
內部類是 Java 物件導向能力的延伸,理解其「是否持有外部引用」這一核心差異,即可推導出所有訪問規則與生命週期特徵。實戰中,靜態內部類應為首選,非靜態內部類按需使用,匿名內部類逐步向 Lambda 遷移,方能寫出清晰、安全、高效的代碼。