兩者均涉及「內部函數/類訪問外部作用域變量」,但實現機制、語義約束截然不同。理解差異有助於跨語言開發避坑。
局部變量默認分配在棧上,方法返回隨棧幀銷毀而釋放。
var quo = function(status) {
return {
get_status: function() {
return status; // 訪問外部函數參數
}
};
};
var myQuo = quo("amazed");
document.writeln(myQuo.get_status()); // "amazed"
status 本應隨 quo 結束銷毀,但內部函數持有引用status 從棧「提升」到堆上的閉包環境中public Runnable f(int x) {
int i = 0; // 局部變量
Runnable r = new Runnable() {
@Override public void run() {
System.out.println(i); // 讀取副本
i = 10; // 編譯錯誤!必須 final 或 effectively final
}
};
i = 100; // 外部修改
return r; // 返回後執行 run() 輸出 0,而非 100
}
i 的值副本 (編譯期確定)final 或「實際不可變」| 維度 | JavaScript 閉包 | Java 匿名內部類 |
|---|---|---|
| 捕獲機制 | 引用捕獲 (Reference Capture) | 值捕獲 (Value Capture / Copy) |
| 變量可變性 | 可讀可寫,雙向同步 | 只讀 (final/effectively final) |
| 內存模型 | 變量提升至堆上閉包環境 | 副本存儲於匿名類實例字段/構造參數 |
| 生命週期 | 隨閉包函數延長 | 隨匿名類實例延長 (副本獨立) |
| 外部修改可見性 | 內部可見 | 內部不可見 (已捕獲副本) |
| this 指向 | 動態 (調用方式決定) | 指向匿名類實例,外部 this 需 Outer.this |
| 語法糖本質 | 函數一等公民 + 詞法作用域 | 編譯器生成實現接口的具體類 |
int i = 0;
Runnable r = () -> { System.out.println(i); }; // i 必須 final 或 effectively final
// i = 1; // 編譯錯誤
Lambda 只是匿名內部類的語法糖,捕獲語義完全一致。
var i 問題),用 let 或 IIFE 修正int[] holder = {0}; 或 AtomicInteger 封裝可變容器JS 閉包是「函數+環境」的一等公民,靈活強大但易洩露內存;Java 匿名內部類是「類實例+副本」的受限閉包,安全確定但表達力弱。兩者解決同一問題(狀態封裝),卻反映了語言設計哲學的根本差異:動態靈活 vs 靜態安全。