this 與 final

June 24, 2022

lambda 運算式並不是匿名函式的語法糖,來看一下接下來的程式,先想想看結果會如何顯示?

package cc.openhome;

import static java.lang.System.out;
 
class Hello {
    Runnable r1 = new Runnable() {
        public void run() {
            out.println(this);
        }
    };
    
    Runnable r2 = new Runnable() {
        public void run() {
            out.println(toString());
        }
    };
 
    public String toString() { 
        return "Hello, world!"; 
    }
}

public class Main {
    public static void main(String[] args) {
        new Hello().r1.run();
        new Hello().r2.run();
    }
}

結果會顯示像是 cc.openhome.Hello$1@6d06d69c 與 cc.openhome.Hello$2@7852e922 之類的訊息,這是因為 this 以及 toString()(也就是 this.toString() )實際對象,實際上會來自匿名類別對應的實例,也就是 Runnable 實例,你沒有定義 RunnabletoString,顯示結果是 Object 預設的 toString 傳回字串。

Lambda運算式 與 this

再來看看接下來的程式,它會顯示什麼?

package cc.openhome;

import static java.lang.System.out;
 
class Hello {
    Runnable r1 = () -> out.println(this);
    Runnable r2 = () -> out.println(toString());
 
    public String toString() { 
        return "Hello, world!"; 
    }
}

public class Main {
    public static void main(String[] args) {
        new Hello().r1.run();
        new Hello().r2.run();
    }
}

如果 Lambda 運算式只是匿名類別的語法蜜糖,那麼結果也該是顯示 cc.openhome.Hello$1@6d06d69c 與 cc.openhome.Hello$2@7852e922 之類的訊息,事實上,執行結果會是顯示兩次的 "Hello, world!"

也就是說,Lambda 運算式本體中的 thistoString()(也就是 this.toString())實際對象,是來自 Lambda 的上下文環境(Context),也就是建構出來的 Hello 實例,因為是 Hello 類別包圍了 Lambda 運算式,範例中定義了 Hello 類別的 toString 傳回 "Hello, world!" 字串,執行時才會顯示兩次的 "Hello, world!"

final 與 Lambda 運算式

如果要在匿名內部類別中存取區域變數,該區域變數必須等效於 final,否則會發生編譯錯誤,例如以下不會編譯錯誤:

String[] names = {"Justin", "Monica", "Irene"}; // 加上 final 也可以
Runnable runnable = new Runnable() {
    public void run() {
        for(String name : names) {
             System.out.println(name);
        }
    }
};

因為 Runnable 符合函式介面定義,可以改為 Lambda 運算式:

String[] names = {"Justin", "Monica", "Irene"};
Runnable runnable = () -> {
    for(String name : names) {
         System.out.println(name);
    }
};

如果 Lambda 運算式中捕獲的區域變數本身等效於 final 區域變數,可以不用在區域變數上加上 final。不過,可以在 Lambda 運算式中改變被捕獲的區域變數值嗎?

不行!Java 特意禁止你捕獲可變動的區域變數(在 JavaScript 等語言可以做到),因為 Java 會想要採用 Lambda 的理由之一,是想進一步支援平行程式設計,Lambda 運算式中可變動的區域變數,通常也代表著運算式本體中會改變被捕獲變數參考的物件狀態,改變物件狀態在平行程式中代表著可能必須處理同步鎖定問題,以禁止捕獲可變動的區域變數來避免了這類的問題。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter