轉自:http://unmi.cc/java-8-lambda-capture-outer-variables/
可能會把捕獲外部變量的 Lambda 表達式稱爲閉包,那麼 Java 8 的 Lambda 可以捕獲什麼變量呢?
- 捕獲實例或靜態變量是沒有限制的(可認爲是通過 final 類型的局部變量 this 來引用前兩者)
- 捕獲的局部變量必須顯式的聲明爲 final 或實際效果的的 final 類型
回顧一下我們在 Java 8 之前,匿名類中如果要訪問局部變量的話,那個局部變量必須顯式的聲明爲 final,例如下面的代碼放在 Java 7 中是編譯不過的
Java 7 要求 version 這個局部變量必須是 final 類型的,否則在匿名類中不可引用。
上面同樣的代碼放到 Java 8 中可以編譯通過,難道 Java 8 不需要 version 是 final 的類型嗎?不盡然
1
2
3
4
5
6
7
8
|
String
version = "1.8" ; foo( new
Supplier() { @Override public
String get() { return
version; } }); version
= "1.7" ;
//在
Java 8 下注釋這行就能編譯通過,否則報出前面同樣的錯誤 |
也就是在 Java 8 下,即使局部變量未聲明爲 final 類型,一旦在匿名類中訪問了一下就被強型加上了 final 屬性,所以後面就無法再次給 version 賦值了。
前面演示了是匿名類,在 Java 8 中換成 Lambda 表達式也是一回事
1
2
3
|
String
version = "1.8" ; foo(()
-> version); //對局部變量
version 的訪問讓 version 變成 final 了 version
= "1.7" ;
//有了這行就編譯不過了 |
因此,Java 8 的 Lambda 表達式訪問局部變量時雖然沒有硬性規定要被聲明爲 final,但實質上是和 Java 7 一樣的。
總之一個局部變量如果要在 Java 7/8 的匿名類或是 Java 8 的 Lambda 表達式中訪問,那麼這個局部變量必須是 final 的,即使沒有 final 飾它也是 final 類型。
注意,並不是 Lambda 開始訪問時那個局部變量才變爲 final,這是編譯器的需求,例如
1
2
3
|
String
version = "1.8" ; version
= "1.7" ;
//註釋掉這行或下行中另一行才能編譯通過 foo(()
-> version ); //這行讓編譯器決定給
version 加上 final 屬性 |
換句話說,如果在匿名類或 Lambda 表達式中訪問的局部變量,如果不是 final 類型的話,編譯器自動加上 final 修飾符。
爲什麼 Lambda 表達式(匿名類) 不能訪問非 final 的局部變量呢?因爲實例變量存在堆中,而局部變量是在棧上分配,Lambda 表達(匿名類) 會在另一個線程中執行。如果在線程中要直接訪問一個局部變量,可能線程執行時該局部變量已經被銷燬了,而 final 類型的局部變量在 Lambda 表達式(匿名類) 中其實是局部變量的一個拷貝。