Spark中Lambda表達式的變量做用域

一般,咱們但願可以在lambda表達式的閉合方法或類中訪問其餘的變量,例如:java

package java8test;安全

 

public class T1 {數據結構

    public static void main(String[] args) {多線程

        repeatMessage("Hello"20);閉包

    }併發

    public static void repeatMessage(String text,int count){this

        Runnable r = () -> {spa

            for(int i = 0; i < count; i++){.net

                System.out.println(text);線程

                Thread.yield();

            }

        };

        new Thread(r).start();

    }

}

注意看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表達式中,被引用的變量的值不能夠被更改。例如,下面這個表達式是不合法的:

public static void repeatMessage(String text,int count){

    Runnable r = () -> {

        while(count > 0){

            count--;        //錯誤,不能更改已捕獲變量的值

            System.out.println(text);

            Thread.yield();

         }

     };

     new Thread(r).start();

}

作出這個約束是有緣由的。更改lambda表達式中的變量不是線程安全的。假設有一系列併發的任務,每一個線程都會更新一個共享的計數器。

int matches = 0;

for(Path p : files)

    new Thread(() -> {if(p中包含某些屬性) matches++;}).start();    //非法更改matches的值

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

不要期望編譯器會捕獲全部併發訪問錯誤。不可變的約束只做用在局部變量上,若是matches是一個實例變量或者閉合類的靜態變量,那麼不會有任何錯誤被報告出來即便結果一樣未定義。一樣,改變一個共享對象也是徹底合法的,即便這樣並不恰當。例如:

List<Path> matches = new ArrayList<>();

for(Path p: files)

//你能夠改變matches的值,可是在多線程下是不安全的

    new Thread(() -> {if(p中包含某些屬性) matches.add(p);}).start();

注意matches是「有效final」的(一個有效的final變量被初始化後,就永遠不會再被賦一個新值的變量)。在咱們的示例中,matches老是引用同一個ArrayList對象,可是,這個對象是可變的,所以是線程不安全的 。若是多個線程同時調用add方法,結果將沒法預測。

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

Path first = Paths.get("/usr/bin");

Comparator<String> comp = (first,second) ->

    Integer.compare(first.length(),second.length());

//錯誤,變量first已經定義了

在一個方法裏,你不能有兩個同名的局部變量,所以,你也不能在lambda表達式中引入這樣的變量。

當你在lambda表達式中使用this關鍵字,你會引用建立該lambda表達式的方法的this參數,如下面的代碼爲例:

public class Application{

    public void doWork(){

        Runnable runner = () -> {....;System.out.println(this.toString());......};

    }

}

表達式this.toString()會調用Application對象的toString()方法,而不是Runnable實例的toString()方法。在lambda表達式中使用this,與在其餘地方使用this沒有什麼不一樣。lambda表達式的做用域被嵌套在doWork()方法中,而且不管this位於方法的何處,其意義都是同樣的。

 

文章收錄,引用自 http://my.oschina.net/fhd/blog/419892

相關文章
相關標籤/搜索