爲什麼lambda表達式要用final

轉自:http://unmi.cc/java-8-lambda-capture-outer-variables/

可能會把捕獲外部變量的 Lambda 表達式稱爲閉包,那麼 Java 8 的 Lambda 可以捕獲什麼變量呢?

  1. 捕獲實例或靜態變量是沒有限制的(可認爲是通過 final 類型的局部變量 this 來引用前兩者)
  2. 捕獲的局部變量必須顯式的聲明爲 final 或實際效果的的 final 類型

回顧一下我們在 Java 8 之前,匿名類中如果要訪問局部變量的話,那個局部變量必須顯式的聲明爲  final,例如下面的代碼放在 Java 7 中是編譯不過的

java7-annymous-capture-local-variable

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 表達式(匿名類) 中其實是局部變量的一個拷貝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章