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()
方法也可作為方法引用傳入。