匿名內部類是 Java 語法糖中最實用、最常見的一種,廣泛應用於事件監聽、多線程、回調接口等場景。本文從字節碼層面解析其本質,並通過豐富實例講解變量捕獲規則、實現方式與最佳實踐。
// 源碼
Runnable r = new Runnable() {
@Override public void run() { System.out.println("Hello"); }
};
// 編譯產出
OuterClass.class
OuterClass$1.class // 匿名內部類編譯為獨立 class 文件,命名規則:外部類$序號
abstract class Animal {
public abstract void sound();
}
Animal dog = new Animal() {
@Override public void sound() { System.out.println("Wang!"); }
};
dog.sound(); // Wang!
interface Swimmable { void swim(); }
interface Flyable { void fly(); }
// 匿名內部類實現多接口 (罕見,通常單接口)
Swimmable duck = new Swimmable() {
@Override public void swim() { System.out.println("Swimming..."); }
};
new 父類/接口() {
// 類體:字段、方法、初始化塊
// 注意:無構造方法、無 static 成員 (常量除外)
};
| 限制 | 說明 |
|---|---|
| 無構造方法 | 無名字無法定義構造器,可用實例初始化塊 { } 替代 | 不能定義 static 字段/方法/類 | 除 static final 編譯期常量 |
| 只能創建一個實例 | 語法即創建實例,無法再次 new |
| 隱式 final | 引用變量隱含 final,不可重新賦值 |
| 訪問外部變量需 final/隱性 final | Java 8+ 允許捕獲「有效 final」局部變量 |
final 修飾的局部變量public void demo() {
int count = 0; // 基本類型:捕獲副本
final int[] arr = {0}; // 引用類型:捕獲引用副本,可修改對象內容
Runnable r = new Runnable() {
@Override public void run() {
// count++; // 編譯錯誤:count 非 final
arr[0]++; // 合法:修改數組內容,而非引用
System.out.println(arr[0]);
}
};
}
編譯器將捕獲的變量作為構造方法參數傳遞給匿名類:
// 反編譯後等價代碼
class Outer$1 implements Runnable {
private final int val$count; // 基本類型:值拷貝
private final int[] val$arr; // 引用類型:引用拷貝
Outer$1(int count, int[] arr) {
this.val$count = count;
this.val$arr = arr;
}
public void run() { ... }
}
// Runnable 匿名類
new Thread(new Runnable() {
@Override public void run() {
for (int i = 1; i <= 5; i++) System.out.print(i + " ");
}
}).start();
// Thread 匿名子類
new Thread() {
@Override public void run() {
for (int i = 1; i <= 5; i++) System.out.print(i + " ");
}
}.start();
button.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
List<String> list = Arrays.asList("c", "a", "b");
Collections.sort(list, new Comparator<String>() {
@Override public int compare(String o1, String o2) {
return o2.compareTo(o1); // 降序
}
});
public interface Callback {
void onSuccess(String data);
void onFailure(Exception e);
}
public void fetchData(Callback cb) {
new Thread(() -> {
try { cb.onSuccess("Data"); }
catch (Exception e) { cb.onFailure(e); }
}).start();
}
// 調用
fetchData(new Callback() {
@Override public void onSuccess(String data) { System.out.println("Got: " + data); }
@Override public void onFailure(Exception e) { e.printStackTrace(); }
});
對於函數式接口 (單一抽象方法),Lambda 更簡潔:
| 匿名內部類 | Lambda 表達式 |
|---|---|
new Runnable() { public void run() {...} } | () -> {...} |
new Comparator() { public int compare(...) {...} } | (a, b) -> {...} |
new ActionListener() { public void actionPerformed(...) {...} } | e -> {...} |
public class Outer {
private String name = "Outer";
public void test() {
Runnable r = new Runnable() {
private String name = "Inner";
@Override public void run() {
System.out.println(this.name); // Inner (匿名類 this)
System.out.println(Outer.this.name); // Outer (外部類 this)
}
};
r.run();
}
}
| 錯誤現象 | 原因 | 修正 |
|---|---|---|
| 捕獲變量編譯錯誤 | 局部變量被修改過,非有效 final | 用 final 數組/AtomicInteger 包裝、或提升為成員變量 |
| 內存洩露 | 匿名類持有外部類引用,異步任務未結束 | 靜態內部類 + WeakReference、或及時取消任務 |
| 序列化失敗 | 匿名類隱式持有外部類引用,外部類未實現 Serializable | 外部類實現 Serializable、或用靜態內部類 |
| A instanceof A == false | 熱部署/多 ClassLoader 導致同類不同身份 | 排除熱部署干擾、統一 ClassLoader |
匿名內部類是 Java 早期缺乏閉包支持時的妥協方案,本質是編譯器生成的獨立類,通過構造器參數捕獲外部變量。Java 8 Lambda 解決了 90% 單方法接口場景,但匿名類在多方法、this 引用、初始化邏輯、序列化等場景仍不可替代。理解其字節碼機制與捕獲語義,能寫出更安全、高效的併發與回調代碼。