在一個類內部定義另一個類,即內部類。它能直接訪問外部類所有成員 (含 private)、隱式持有外部類引用、實現多重繼承變相、邏輯分組封裝。本文系統講解成員內部類、局部內部類、匿名內部類、靜態嵌套類四大類型、作用域、實例化語法、靜態與非靜態本質區別、常見陷阱。
| 類型 | 定義位置 | 訪問外部類成員 | 實例化語法 | 能否定義 static 成員 | 典型場景 |
|---|---|---|---|---|---|
| 成員內部類 | 類內部、方法外 | 直接訪問 (含 private) | Outer.Inner inner = new Outer().new Inner(); |
❌ (JDK 16+ 允許 static final) | 邏輯強關聯、需頻繁訪問外部狀態 |
| 靜態嵌套類 | 類內部、方法外 + static |
僅靜態成員 | Outer.Nested nested = new Outer.Nested(); |
✅ | 工具類、構建器、不依賴外部實例 |
| 局部內部類 | 方法/代碼塊內部 | 直接訪問 + final/效果上 final 的局部變量 | 方法內 new LocalClass() |
❌ | 臨時邏輯封裝、線程任務、事件監聽 |
| 匿名內部類 | 表達式中 (new Interface(){...}) | 同局部內部類 | new Runnable() { public void run(){...} } |
❌ | 一次性實現接口/繼承類、Lambda 前身 |
public class Outer {
private String name = "Outer";
private static String staticField = "static";
public class Inner {
public void print() {
System.out.println(name); // 直接訪問外部私有實例字段
System.out.println(staticField); // 訪問外部靜態字段
System.out.println(Outer.this.name); // 顯式獲取外部實例引用
}
}
public static void main(String[] args) {
// 實例化:必須先有外部實例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 語法糖:outer.new Inner()
inner.print();
}
}
// Inner.class 實際簽名
class Outer$Inner {
final Outer this$0; // 隱式字段,持有外部實例引用
public Outer$Inner(Outer outer) { this$0 = outer; }
public void print() { System.out.println(this$0.name); }
}
結論:非靜態內部類實例不能脫離外部實例存在,會導致外部實例無法 GC (內存洩漏隱患)。
public class Outer {
private String name = "Outer";
private static String staticField = "static";
public static class Nested {
public void print() {
// System.out.println(name); // 編譯錯誤:無法訪問非靜態字段
System.out.println(staticField); // 僅能訪問靜態成員
// Outer.this 不存在
}
}
public static void main(String[] args) {
// 實例化:無需外部實例
Outer.Nested nested = new Outer.Nested();
nested.print();
}
}
本質:static class = 頂級類,只是命名空間嵌套在 Outer 下,無隱式引用,不持有外部實例,安全無內存洩漏。
public class Outer {
public void method(final String param) { // JDK 8+ 可省略 final,但必須「效果上 final」
String localVar = "local";
class LocalInner {
public void print() {
System.out.println(param); // 捕獲參數
System.out.println(localVar); // 捕獲局部變量
}
}
new LocalInner().print(); // 僅在方法內可實例化
}
}
// JDK 7 風格
Runnable task = new Runnable() {
@Override public void run() {
System.out.println("Anonymous run");
}
};
new Thread(task).start();
// 對比 Lambda (JDK 8+)
Runnable task2 = () -> System.out.println("Lambda run");
| 陷阱 | 現象 | 解決 |
|---|---|---|
| 非靜態內部類導致外部類無法 GC | 長生命周期內部類實例 (如 Handler、監聽器) 持有外部 Activity/Context | 改為 static 內部類 + WeakReference 引用外部 |
| 序列化非靜態內部類 | 連帶序列化外部類實例,甚至拋 NotSerializableException | 避免序列化內部類,或改靜態嵌套類 |
| 匿名類 this 指向問題 | 匿名類中 this 指向匿名類實例,非外部類 |
用 Outer.this 訪問外部實例 |
| 局部類訪問非 final 變量 | 編譯錯誤:Variable used in lambda expression should be final or effectively final | 改為 final 或用單元素數組/AtomicReference 包裝 |
一句话心法:內部類四兄弟,**靜態嵌套類最乾淨**,成員內部類最強大但最危險,局部/匿名類最輕量但作用域受限。