天天看點

Java 8 之 lambda 變量作用域

通常,我們希望能夠在lambda表達式的閉合方法或類中通路其他的變量,例如:

注意看lambda表達式中的變量count和text,它們并沒有在lambda表達式中被定義,而是方法repeatmessage的參數變量。如果你思考一下,就會發現這裡有一些隐含的東西。lambda表達式可能會在repeatmessage傳回之後才運作,此時參數變量已經消失了。如果保留text和count變量會怎樣呢?

為了了解這一點,我們需要對lambda表達式有更深入的了解。一個lambda表達式包括三個部分:

一段代碼

參數

自由變量的值,這裡的“自由”指的是那些不是參數并且沒有在代碼中定義的變量。

在我們的示例中,lambda表達式有兩個自由變量,text和count。資料結構表示lambda表達式必須存儲這兩個變量的值,即“hello”和20。我們可以說,這些值已經被lambda表達式捕獲了(這是一個技術實作的細節。例如,你可以将一個lambda表達式轉換為一個隻含一個方法的對象,這樣自由變量的值就會被複制到該對象的執行個體變量中)。

注意:含有自由變量的代碼塊才被稱之為“閉包(closure)”。在java中,lambda表達式就是閉包。事實上,内部類一直都是閉包。java8中為閉包賦予了更吸引人的文法。

如你所見,lambda表達式可以捕獲閉合作用域中的變量值。在java中,為了確定被捕獲的值是被良好定義的,需要遵守一個重要的限制。在lambda表達式中,被引用的變量的值不可以被更改。例如,下面這個表達式是不合法的:

做出這個限制是有原因的。更改lambda表達式中的變量不是線程安全的。假設有一系列并發的任務,每個線程都會更新一個共享的計數器。

如果這段代碼是合法的,那麼會引起十分糟糕的結果。自增操作matches++不是原子操作,如果多個線程并發執行該自增操作,天曉得會發生什麼。

不要指望編譯器會捕獲所有并發通路錯誤。不可變的限制隻作用在局部變量上,如果matches是一個執行個體變量或者閉合類的靜态變量,那麼不會有任何錯誤被報告出來即使結果同樣未定義。同樣,改變一個共享對象也是完全合法的,即使這樣并不恰當。例如:

注意matches是“有效final”的(一個有效的final變量被初始化後,就永遠不會再被賦一個新值的變量)。在我們的示例中,matches總是引用同一個arraylist對象,但是,這個對象是可變的,是以是線程不安全的 。如果多個線程同時調用add方法,結果将無法預測。

lambda表達式的方法體與嵌套代碼塊有着相同的作用域。是以它也适用同樣的命名沖突和屏蔽規則。在lambda表達式中不允許聲明一個與局部變量同名的參數或者局部變量。

public class application{

}