java8之lambda表達式(變量作用域)

jopen 9年前發布 | 21K 次閱讀 Java開發 Java8

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

package java8test;

public class T1 {     public static void main(String[] args) {         repeatMessage("Hello", 20);     }     public static void repeatMessage(String text,int count){         Runnable r = () -> {             for(int i = 0; i < count; i++){                 System.out.println(text);                 Thread.yield();             }         };         new Thread(r).start();     } }</pre>

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

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

  • 一段代碼

    </li>

  • 參數

    </li>

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

    </li> </ul>

    在我們的示例中,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

    </div>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!