匿名內部類是 Java「語法糖」的經典代表,編譯器自動生成實現類/子類並實例化。本文深度解析:編譯後字節碼結構、捕獲外部變量機制、兩大核心實戰場景 (實現介面/繼承類、解決多重繼承限制)、Lambda 時代的定位。
// 源碼
Runnable r = new Runnable() {
@Override public void run() { System.out.println("Hello"); }
};
// 編譯器生成 (概念等價)
class AnonymousRunnable implements Runnable {
@Override public void run() { System.out.println("Hello"); }
}
Runnable r = new AnonymousRunnable();
實際編譯產生 OuterClass$1.class,構造器隱式持有外部類引用 (OuterClass.this)。
public void demo() {
String msg = "Hello"; // effectively final (Java 8+)
Runnable r = new Runnable() {
@Override public void run() {
System.out.println(msg); // 捕獲外部局部變量
}
};
}
機制:編譯器將捕獲變量作為構造器參數傳入,在匿名類中生成 final 副本字段。因此要求變量不被修改 (final 或 effectively final)。
// 1. 線程啟動
new Thread(() -> System.out.println("Run")).start(); // Java 8+ Lambda 更簡潔
// 2. 集合排序
List list = Arrays.asList("b", "a", "c");
list.sort(new Comparator() {
@Override public int compare(String a, String b) { return b.compareTo(a); } // 降序
});
// 3. Swing 事件監聽
button.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) { doSomething(); }
});
// 4. Spring Bean 後置處理器
@Bean
public BeanPostProcessor processor() {
return new BeanPostProcessor() {
@Override public Object postProcessBeforeInitialization(Object bean, String name) {
return bean;
}
};
}
問題:類 Child 想同時繼承 Parent.count() 並實現 ICount.count(),但方法簽名相同無法區分。
interface ICount { int count(); }
class Parent {
int i = 0;
int count() { return i++; }
}
// 匿名內部類破解
class Child extends Parent {
ICount getCounter() {
return new ICount() { // 匿名類實現 ICount
int i = 0; // 獨立狀態
@Override public int count() { return i *= 2; } // 不同邏輯
};
}
}
// 使用
Child child = new Child();
System.out.println(child.count()); // Parent 邏輯: 0, 1, 2...
System.out.println(child.getCounter().count()); // ICount 邏輯: 0, 0, 0... (i*=2)
System.out.println(child.getCounter().count()); // 0
核心價值:將「實現」從「類繼承體系」中剝離,避免單繼承限制,實現「一個類,多種行為實現」。
| 維度 | 匿名內部類 | Lambda |
|---|---|---|
| 適用目標 | 任意介面/抽象類/具體類 | 僅函數式介面 (單一抽象方法) |
| this 引用 | 指向匿名類實例 | 指向外部類實例 |
| 狀態 | 可定義字段/方法 | 無狀態 (僅捕獲最終變量) |
| 序列化 | 可序列化 (若實現 Serializable) | 不可序列化 (標準規定) |
| 遞歸 | 可用 this 遞歸調用 | 需借助字段/方法引用 |
| 性能 | 每次創建新實例 | 可能用 invokedynamic 緩存實例 |
// 編譯後
$ javap -c OuterClass$1.class
Compiled from "OuterClass.java"
class OuterClass$1 implements Runnable {
final OuterClass this$0; // 隱式外部類引用
final String val$msg; // 捕獲變量副本
OuterClass$1(OuterClass, String); // 構造器接收外部引用 + 捕獲值
public void run(); // 方法體
}
匿名內部類是 Java 早期「函數式編程」的變通方案,核心價值在於:內聯實現介面/類、捕獲外部上下文、繞過單繼承限制。Java 8+ Lambda 覆蓋了 80%+ 場景,但非函數式介面、需攜帶狀態/序列化/遞歸、邏輯較複雜時,匿名內部類仍是不可替代的利器。理解其編譯機制 (隱式外部引用、捕獲變量副本),能寫出更安全、高效的代碼。