2015-08-28 89 views
5

請向我解釋lambda表達式如何使用和修改它的封裝類的實例變量,並且只能使用它的封閉範圍的局部變量(除非它是final或有效的final)? 我的基本問題是類的實例變量中的λ是如何修改的,並且局部變量不是在範圍Lambda表達式和變量捕獲

+2

由於lambda實際上是一個匿名內部類,這是[爲什麼只有最終變量可以在匿名類中訪問?](http://stackoverflow.com/questions/ 4732544/why-only-final-variables-accessible-in-anonymous-class) – mthmulders

+1

對於Java 8來說這是真實的,但據我所知它沒有被指定,所以這可以在未來版本的Java中改變。 – Alex

+3

只有'this'被捕獲。所有對lambda體內實例變量的訪問都是通過'this.field'完成的,因此你也可以寫入它。 – ZhongYu

回答

8

方面首先,我們可以看看在JLS,其中規定了以下內容:

使用但未在lambda表達式中聲明的任何局部變量,形式參數或異常參數都必須聲明爲final或有效最終(§4.12.4),否則會發生編譯時錯誤,其中嘗試使用。

任何使用但未在lambda體中聲明的局部變量必須在lambda體之前明確賦值(§16(Definite Assignment)),否則會發生編譯時錯誤。

有關變量使用的相似規則適用於內部類的主體(第8.1.3節)。對有效最終變量的限制禁止訪問動態更改的局部變量,它們的捕獲可能會引入併發問題。與最終的限制相比,它減少了程序員的文書負擔。

對有效最終變量的限制包括標準循環變量,但不包括增強型for循環變量,循環變量在循環的每次迭代中都被視爲不同(第14.14.2節)。


爲了更好地理解它,看看這個例子類:

輸出的
public class LambdaTest { 

    public static void main(String[] args) { 
     LambdaTest test = new LambdaTest(); 
     test.returnConsumer().accept("Hello"); 
     test.returnConsumerWithInstanceVariable().accept("Hello"); 
     test.returnConsumerWithLocalFinalVariable().accept("Hello"); 
    } 

    String string = " world!"; 

    Consumer<String> returnConsumer() { 
     return ((s) -> {System.out.println(s);}); 
    } 

    Consumer<String> returnConsumerWithInstanceVariable() { 
     return ((s) -> {System.out.println(s + string);}); 
    } 

    Consumer<String> returnConsumerWithLocalFinalVariable() { 
     final String foo = " you there!"; 
     return ((s) -> {System.out.println(s + foo);}); 
    } 

} 

主要是

Hello 
Hello world! 
Hello you there! 

這是因爲這裏返回拉姆達是多少與創建new Consumer<String>() {...}的新匿名類相同。你的lambda - 一個Consumer<String>的實例有一個對它創建的類的引用。你可以重寫returnConsumerWithInstanceVariable來使用System.out.println(s + LambdaTest.this.string),這將完全相同。這就是允許你訪問(和修改)實例變量的原因。

如果你的方法中有一個(有效的)最終局部變量,你可以訪問它,因爲你可以訪問它,因爲它被複制到你的lambda實例。

但是,但是,如果它不是終點,你覺得在以下方面應該發生:

Consumer<String> returnConsumerBad() { 
    String foo = " you there!"; 
    Consumer<String> results = ((s) -> {System.out.println(s + foo);}); 
    foo = " to all of you!"; 
    return results; 
} 

如果該值被複制到您的實例,但後來沒有更新,當局部變量更新?這可能會引起混淆,因爲我認爲許多程序員會希望富美在返回此lambda後擁有「給所有人」的新價值。

如果你有一個原始值,它會放在堆棧上。所以你不能簡單地引用局部變量,因爲它可能會在方法結束後消失。

0

你可以參考這篇文章 - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood關於lambda表達式編譯的解釋。正如所解釋的lambda表達式/代碼塊被編譯到匿名類中,這些匿名類將使用名稱格式(<<Enclosing Class name>>$<<1(Number)>>)進行編譯,因此假設假設允許非最終局部變量,那麼編譯器無法從引用該局部變量的位置追蹤它因爲匿名類的.class文件是用前述格式創建/編譯的,就像普通的java類一樣。

因此,如果局部變量是最終的,那麼編譯器會在不會給編譯器造成歧義的類動態類中創建一個最終實例。 請參考上面的鏈接提供更多信息