lambda 表示式為什麼不能修改區域性變數
阿新 • • 發佈:2021-09-19
程式碼示例
public static void main(String[] args) {
int value = 0;
IntStream.range(0, 10).forEach(i -> value++);
System.out.println(value);
}
這段程式碼中第三行的 value 會出現以下的錯誤提示:
Variable used in lambda expression should be final or effectively final
原因分析
首先,我們明確以下幾點內容:
value
是一個區域性變數。forEach(i -> value++)
value++
屬於在 lambda 表示式中修改區域性變數。- 我們可以把 lambda 表示式看作是一個匿名內部類例項化出來的物件。
一、
在Java的執行緒模型中,棧幀中的區域性變數是執行緒私有的,永遠不需要進行同步。但是如果允許通過匿名內部類把棧幀中的變數地址洩漏出去(逃逸),那麼就會引發非常可怕的後果:一份“本來被 Java 執行緒模型規定為永遠是執行緒私有的資料”將可能被併發訪問!哪怕它不被併發訪問,棧中變數的記憶體地址洩漏到棧幀之外這件事本身已經足夠危險了,這是Java這種記憶體安全的語言絕對無法容忍的。
二、
其實這段程式碼中的區域性變數 value
和 forEach(i -> value++)
value
的值 copy 了過去,因此它們是兩個不同的"符號"。同時 Java 為了防止我們在 lambda 中修改其隱式創建出來的變數時,將內外的這兩個同名"符號"誤以為是同一個變數,就規定這種變數必須是 final 或 等效於 final 的,不能修改。
解決方案
如果我們就是想將這兩個變數作為同一個變數使用,可以藉助於在堆中建立的物件來實現。
public static void main(String[] args) { int[] value = new int[1]; IntStream.range(0, 10).forEach(i -> value[0]++); System.out.println(value[0]); }
其他疑問
既然可以通過上面的方法藉助堆中的物件可以實現在 lambda 中修改外部的變數,那麼為什麼下面的程式碼依然報錯?
public static void main(String[] args) {
Integer value = new Integer(0);
IntStream.range(0, 10).forEach(i -> value++);
System.out.println(value);
}
public static void main2(String[] args) {
int[] value = new int[1];
IntStream.range(0, 10).forEach(i -> value = new int[1]);
System.out.println(value[0]);
}
原因: 第一段程式碼通過 Integer 傳值給 lambda 的思路是沒有問題的,但是 value++
卻又將傳遞進來的 value 的值給替換了 (Java 中是值傳遞的,只不過對於物件引數,值的內容是物件的引用),第二段程式碼中的 value = new int[1]
也是同理,它們都是將 value 的引用給修改了, 而 lambda 是不允許這種修改的,所以產生了報錯。