1. 程式人生 > 其它 >Java的Lambda表示式

Java的Lambda表示式

函數語言程式設計(Functional Programming)是把函式作為基本運算單元,函式可以作為變數,可以接收函式,還可以返回函式。歷史上研究函數語言程式設計的理論是Lambda演算,所以我們經常把支援函數語言程式設計的編碼風格稱為Lambda表示式。

在Java程式中,我們經常遇到一大堆單方法介面,即一個介面只定義了一個方法:

  • Comparator
  • Runnable
  • Callable

Comparator為例,我們想要呼叫Arrays.sort()時,可以傳入一個Comparator例項,以匿名類方式編寫如下:

String[] array = ...
Arrays.sort(array, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

上述寫法非常繁瑣。從Java 8開始,我們可以用Lambda表示式替換單方法介面。改寫上述程式碼如下:

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, (s1, s2) -> {
            return s1.compareTo(s2);
        });
        // 輸出:Apple, Banana, Lemon, Orange
        System.out.println(String.join(", ", array));
    }
}

觀察Lambda表示式的寫法,它只需要寫出方法定義:

(s1, s2) -> {
    return s1.compareTo(s2);
}

其中,引數是(s1, s2),引數型別可以省略,因為編譯器可以自動推斷出String型別。-> { ... }表示方法體,所有程式碼寫在內部即可。返回值的型別也是由編譯器自動推斷的,這裡推斷出的返回值是int,因此,只要返回int,編譯器就不會報錯。

並且不用書寫class定義,這樣的寫法是不是非常簡潔。

如果只有一行return xxx的程式碼,完全可以用更簡單的寫法:

Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

FunctionalInterface

我們把只定義了單方法的介面稱之為FunctionalInterface,用註解@FunctionalInterface標記。這裡以Callable介面為例:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

再來看Comparator介面:

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        ...
    }
    ...
}

這時候看到Comparator介面,一些人認為它不應該是定義單方法的介面。

如果你細看,雖然Comparator介面有很多方法,但只有一個抽象方法int compare(T o1, T o2),其他的方法都是default方法或static方法。還有boolean equals(Object obj);Object定義的方法,不算在介面方法內。因此,Comparator也是一個FunctionalInterface

看到這有興趣的還能再補充下

方法引用

除了Lambda表示式,我們還可以直接傳入方法引用。例如:

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);
        System.out.println(String.join(", ", array));
    }

    static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

上述程式碼在Arrays.sort()中直接傳入了靜態方法cmp的引用,用Main::cmp表示。

因此,所謂方法引用,是指如果某個方法簽名和介面恰好一致,就可以直接傳入方法引用。

那什麼是方法簽名和介面一致呢?

方法引數一致,返回型別相同,我們說兩者的方法簽名一致。(不看方法名稱,也不看類的繼承關係)

Comparator<String>介面定義的方法是int compare(String, String),和靜態方法int cmp(String, String)就屬於方法簽名一致,我們可以直接把方法名作為Lambda表示式傳入:

Arrays.sort(array, Main::cmp);

我們再看看如何引用例項方法:

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, String::compareTo);
        System.out.println(String.join(", ", array));
    }
}

不但可以編譯通過,而且執行結果也是一樣的,這說明String.compareTo()方法也符合Lambda定義。

觀察String.compareTo()的方法定義:

public final class String {
    public int compareTo(String o) {
        ...
    }
}

有人會疑惑了:這個方法的簽名只有一個引數,為什麼和int Comparator<String>.compare(String, String)能匹配呢?

因為例項方法有一個隱含的this引數,String類的compareTo()方法在實際呼叫的時候,第一個隱含引數總是傳入this,相當於靜態方法:

public static int compareTo(this, String o);

所以,String.compareTo()方法也可作為方法引用傳入。