內部類是 Java 語法糖的集中體現,編譯器通過生成隱式構造器、外部類引用字段、訪問權限橋接方法,實現內部類對外部成員的無縫訪問。掌握四大類型的語義邊界、捕獲語義、序列化陷阱、Lambda 替代關係,能幫你寫出更簡潔、封裝性更強的代碼。本文從語法、字節碼、最佳實踐三個維度徹底講透。
| 類型 | 定義位置 | 可用修飾符 | 隱式持有 | 訪問外部成員 | 實例化方式 | 典型場景 |
|---|---|---|---|---|---|---|
| 成員內部類 | 類內部、方法外 | public/protected/private/static/final/abstract | 外部類實例 (Outer.this) | 直接訪問所有(含 private) | new Outer().new Inner() | 迭代器、節點、UI 組件內部邏輯 |
| 靜態嵌套類 | 類內部、方法外 + static | 同成員類 | 無 | 僅靜態成員 | new Outer.StaticNested() | Builder、DTO、工具類、緩存條目 |
| 局部內部類 | 方法/代碼塊內部 | 無(隱式 final) | 外部類實例 + 最終局部變量 | 外部所有 + final 局部變量 | 方法內 new LocalClass() | 臨時封裝複雜邏輯、線程任務 |
| 匿名內部類 | 表達式位置 | 無 | 外部類實例 + 最終局部變量 | 外部所有 + final 局部變量 | new SuperType() { ... } | 事件監聽、Runnable、比較器、模板方法鉤子 |
// 源碼
class Outer {
private int outerField = 1;
class Inner {
void access() { System.out.println(outerField); }
}
}
// 編譯器生成 (反編譯)
class Outer$Inner {
final Outer this$0; // 隱式引用
Outer$Inner(Outer this$0) { this.this$0 = this$0; }
void access() { System.out.println(this$0.outerField); }
// 若訪問 private,編譯器在 Outer 生成靜態橋接方法:
// static int access$000(Outer o) { return o.outerField; }
}
void method(final int param, int local) {
local = 2; // 非 final,不可捕獲
Runnable r = () -> System.out.println(param); // param 隱式 final
// 等價匿名類生成:
// class Outer$1 implements Runnable {
// final int val$param; // 複製值
// Outer$1(int param) { this.val$param = param; }
// public void run() { System.out.println(val$param); }
// }
}
本質:值複製。Java 8+ 允許「實際最終」變量(不重新賦值),編譯器自動視為 final。
public class MemberInnerDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.callInner(); // 通過外部實例創建內部實例
// 外部類靜態方法中創建:需先有外部實例
Outer.Inner inner = new Outer().new Inner();
inner.innerMethod();
}
}
class Outer {
private String name = "Outer";
class Inner {
private String name = "Inner";
void innerMethod() {
System.out.println(this.name); // Inner
System.out.println(Outer.this.name); // Outer
}
}
void callInner() { new Inner().innerMethod(); }
}
public class StaticNestedDemo {
public static void main(String[] args) {
// 無需 Outer 實例
Outer.Nested nested = new Outer.Nested();
nested.print(); // 只能訪問 Outer 靜態成員
}
}
class Outer {
private static String staticField = "Static";
private String instanceField = "Instance"; // 編譯錯誤若訪問
static class Nested {
void print() { System.out.println(staticField); }
}
}
public class LocalInnerDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.process(42);
}
}
class Outer {
private int base = 10;
void process(final int param) { // param 必須 final 或 effectively final
int local = 100; // effectively final
class Local {
void compute() {
System.out.println(base + param + local); // 152
}
}
new Local().compute();
}
}
public class AnonymousDemo {
public static void main(String[] args) {
// 實現接口
Runnable r = new Runnable() {
@Override public void run() { System.out.println("匿名 Runnable"); }
};
r.run();
// 繼承抽象類
AbstractWorker worker = new AbstractWorker() {
@Override void execute() { System.out.println("匿名 Worker"); }
};
worker.execute();
// 帶構造器參數(僅繼承類可用)
Thread t = new Thread("Anonymous-Thread") {
@Override public void run() { System.out.println(getName() + " running"); }
};
t.start();
}
}
abstract class AbstractWorker { abstract void execute(); }
| 陷阱 | 原因 | 對策 |
|---|---|---|
| 內部類實例持有外部類引用導致內存洩漏 | 非靜態內部類實例生命週期超過外部類預期(如 Handler、AsyncTask、監聽器) | 改為 static 內部類 + WeakReference<Outer>;或使用 Lambda(無 this$0) |
| 序列化失敗 | 非靜態內部類默認序列化會嘗試序列化外部類實例 | 實現 Serializable 並 transient 外部引用;或改靜態嵌套類 |
| 匿名類在循環中創建產生大量 Class 對象 | 每個匿名類表達式編譯生成獨立 .class 文件 | 提取為具名靜態類、Lambda、或方法引用 |
| 局部類訪問非 final 變量編譯錯誤 | 捕獲的是值副本,變量可變會導致語義混亂 | 重構為 final 變量、或用單元素數組/AtomicReference 包裝 |
| Lambda 替代匿名類的限制 | Lambda 無 this 引用、無法實現多方法接口、無法繼承類 | 需 this 引用、多方法、繼承類時保留匿名類 |
invokedynamic 指令,運行時生成類,無 this$0 引用,不捕獲外部實例,內存更輕量。Runnable r = () -> {}; vs new Runnable() {}:前者無外部引用,後者隱式持有 Outer.this。obj::method 同理。