在 Java 中,定義在方法內部的類稱為局部內部類。這類類別有一個著名的限制:它們只能訪問方法中被 final 修飾的局部變量(Java 8 之後實際上是「有效 final」)。這究竟是為什麼?我們通過一個具體例子來深入分析。
package com.study;
interface OutInterface2 { }
class OuterClass3 {
public OutInterface2 doit(final String x) { // 參數標記為 final
// 在 doit() 方法中定義局部內部類
class InnerClass2 implements OutInterface2 {
InnerClass2(String s) {
s = x; // 這裡發生了什麼?
System.out.println(s);
}
}
return new InnerClass2("doit"); // "doit" 參數有什麼意義?
}
}
s = x 這行代碼的含義?"doit" 還有意義嗎?System.out.println(s) 輸出的是 x 的內容而不是 "doit"?方法執行結束後,其棧幀會銷毀,局部變量隨之消失。但內部類實例可能在方法返回後長期存活(如被外部引用)。為了讓內部類能繼續使用該變量,Java 編譯器會隱式地將該變量的值複製到內部類實例的字段中。
如果允許修改局部變量,會產生兩份不同的值:一份在方法棧幀中,一份在內部類實例中,導致數據不一致。final 保證變量引用不可變,從而確保複製的值與原始值始終同步。
s = x:構造方法參數 s 接收外部傳入的 "doit",但緊接著被外部 final 變量 x 的值覆蓋。new InnerClass2("doit"):這個參數實際上被構造方法內部的賦值語句覆蓋了,對最終輸出無影響。x 的值,因為賦值操作 s = x 在打印語句之前執行。局部內部類訪問外部局部變量的本質是值捕獲。編譯器在內部類中生成對應的字段,並在構造時將外部變量的值拷貝進去。final 限制確保了這種拷貝語義的安全性。理解這一機制,對於正確使用匿名內部類、Lambda 表達式以及避免內存洩漏至關重要。