在正式看Lambda之前,先來看個匿名類別的應用場合,舉例而言,將名稱依長度進行排序:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, new Comparator<String>() {
public int compare(String name1, String name2) {
return name1.length() - name2.length();
}
});
Arrays
的sort()
方法可以用來排序,只不過,你得告訴他兩個元素比較時順序為何,sort()
規定你得實作java.util.Comparator
來說明這件事,然而那個匿名類別的語法有些冗長,如果想稍微改變一下Arrays.sort()
該行的可讀性,可以如下:
Comparator<String> byLength = new Comparator<String>() {
public int compare(String name1, String name2) {
return name1.length() - name2.length();
}
};
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, byLength);
透過變數byLength
,確實是可以讓排序的意圖清楚許多,只是實作java.util.Comparator
時的匿名類別時依舊冗長,有太多重複的資訊,如果使用JDK8的話,你可以使用Lambda特性去除重複的資訊,我們一步一步來。
例如,宣告byLength
時已經寫了一次Comparator<String>
,為什麼實作匿名類別時又得寫一次Comparator<String>
?使用JDK8的Lambda特性的話,可以寫為:
Comparator<String> byLength = (String name1, String name2) -> name1.length() - name2.length();
重複的Comparator<String>
資訊從等號右邊去除了,而因為這個匿名類別只有一個方法要實作,因此實際上從等號左邊的Comparator<String>
宣告就可以知道,實際上是要實作Comparator<String>
的compare()
方法。仔細看看,還有重複的資訊,既然宣告變數時使用了Comparator<String>
,為什麼的參數上又得宣告一次String
?實際上確實不用,因為編譯器確實可以從變數的宣告型態得知這個資訊,因此可以再簡化為:
Comparator<String> byLength = (name1, name2) -> name1.length() - name2.length();
等號右邊的運算式是夠簡短了,讓我們將它直接放到Arrays
的sort()
方法中:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, (name1, name2) -> name1.length() - name2.length());
因為編譯器可以從names
推斷,sort()
方法的第二個參數型態實際上就是Comparator<String>
,因而name1
與name2
還是不用宣告型態;跟一開始的匿名類別寫法相比較,這邊的程式碼確實是簡潔許多,那麼,JDK8的Lambda只是匿名類別的語法蜜糖嗎?不!還有許多細節會在後續介紹,現在還是先集中重複性的去除與可讀性的改善。
如果你在許多地方,都會有依字串長度排序的需求,那你會怎麼做?如果是同一個方法內,那麼就像之前,用個byName
區域變數吧!如果是多個方法間要共用,那就用個byName
的值域(Field)成員吧!因為byName
要參考的實例沒有狀態問題,因而宣告為static
比較適合,如果要在多個類別之間共用,那麼就設定為public static
如何?例如:
package cc.openhome;
public class StringOrder {
public static int byLength(String s1, String s2) {
return s1.length() - s2.length();
}
public static int byLexicography(String s1, String s2) {
return s1.compareTo(s2);
}
public static int byLexicographyIgnoreCase(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
}
這次你聰明一些了,將一些字串排序時可能的方式都定義出來了,原本的依名稱長度排序就可以改寫為:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, (name1, name2) -> StringOrder.byLength(name1, name2));
也許你發現了,除了方法名稱之外,byLength
方法的簽署與Comparator
的compare
方法相同,我們只是在Lambda運算式中將參數s1
與s2
傳給byLength
方法,這不是重複是什麼?可以直接重用byLength
方法的實作不是更好嗎?是的,JDK8 提供了方法參考的特性,可以達到這個目的:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, StringOrder::byLength);
在Java中引入Lambda的同時,與現有API維持相容性是主要考量之一。方法參數(Method reference)在重用現有API上扮演了重要的角色。重用現有的方法實作,可避免到處寫下Lambda運算式。上面的例子是運用了方法參考中的一種形式,參考了static
方法。
現在來看看另一個需求,如果想依字典順序排序名稱呢?因為你已經定義了StringOrder
,也許你會這麼撰寫:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, StringOrder::byLexicography);
StringOrder
的byLexicography()
,只不過是呼叫String
的compareTo()
方法,也就是將第一個參數s1
作為compareTo()
主詞,第二個參數s2
作compareTo()
方法的受詞,在這種情況下,其實我們可以直接參考String
類別的compareTo
方法,例如:String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, String::compareTo);
類似地,想對名稱按照字典順序排序,但忽略大小寫差異,也不用再透過
StringOrder
的static
方法了,只需要直接參考String
的compareToIgnoreCase()
方法:String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, String::compareToIgnoreCase);
可輕易觀察到,方法參考不僅避免了重複撰寫Lambda運算式,也可以讓程式碼更為清楚。
這邊只是初嘗一下Lambda的甜頭,關於Lambda還有更多細節,後續我們再來一一探討。