
認識 Lambda/Closure(8)方法參考與建構式參考
認識 Lambda/Closure(7)JDK8 Lambda 語法 << 前情 根據名稱的長度進行排序,可以如下撰寫程式: List<String> names = Arrays.asList("Justin", "Monica", "Irene", "caterpillar"); Collections.sort(names, new Comparator<String>() { public int compare(String s1, String s2) { return s1.length() - s2.length(); } }); 如果你單只是看 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); } ... } 現在,你可以將先前的程式碼改寫為以下: Collections.sort(names, new Comparator<String>() { public int compare(String s1, String s2) { return StringOrder.byLength(s1, s2); } }); 程式打算做些什麼,現在看來是清楚多了。使用 JDK8 Lambda 的話,可以讓這個程式碼變得更清楚些。 Collections.sort(names, (s1, s2) -> NameOrder.byLength(s1, s2)); 也許有個聰明的傢伙發現了,除了方法名稱之外, Collections.sort(names, NameOrder::byLength); 在 Java 中引人 Lambda 的同時,與現有 API 維持相容性是主要考量之一。除了採用函式介面之外,方法參數(Method reference)在重用現有 API 上也扮演了重要的角色。重用現有的方法實作,可避免到處寫下 Lambda 運算式。上面的例子是運用了方法參考中的一種形式 – 參考了 Collections.sort(names, NameOrder::byLexicography); 從先前的段落說明中,我們知道 Collections.sort(names, (s1, s2) -> s1.compareTo(s2)); 我們可以發現到,在 Lambda 運算式的本體部份,第一個參數 Collections.sort(names, String::compareTo); 類似地,想對名稱清單按照字典順序排序,但忽略大小寫差異,本來可以如下參考 Collections.sort(names, NameOrder::byLexicographyIgnoreCase); 再次地,在 Collections.sort(names, String::compareToIgnoreCase); 可輕易觀察到,方法參考不僅避免了重複撰寫 Lambda 運算式,也可以讓程式碼更為清楚。除了以下兩種方法參考形式外,我們還可以參考特定物件的實例方法。例如,假設你正在設計一個可以過濾職缺應徵者的軟體,而你有以下兩個類別: public class JobVacancy { ... public int bySeniority(JobApplicant ja1, JobApplicant ja2) { ... } public int byEducation(JobApplicant ja1, JobApplicant ja2) { ... } ... } public class JobApplicant { ... } 如果你使用 JDK8,並如下撰寫 Lambda 演算式來進行應徵者的排序: List<JobApplicant> applicants = ...; JobVacancy vacancy = ...; Collections.sort(applicants, (ja1, ja2) -> vacancy.bySeniority(ja1, ja2)); Lambda 運算式捕捉了 Collections.sort(applicants, vacancy::bySeniority); 除了方法參考之外,JDK8 還提供了建構式參考(Constructor references)。你也許會發出疑問:「建構式?他們有傳回值型態嗎?」有的!其實每個建構式都會有傳回值型態 – 也就是定義他們的類別本身。例如,若你有以下的介面: public interface Part { ... } public interface Material { ... } public interface PartFactory { Part createPart(Material material); } 你為這些介面撰寫了一些實作: public class PartImpl implements Part { public PartImpl(Material material) { ... } } public class MaterialImpl implements Material { ... } public PartFactoryImpl implements PartFactory { public Part createPart(Material material) { return new PartImpl(material); } } 接著,你可能使用以下的程式碼來建立 PartFactory factory = new PartFactoryImpl(); Part part = factory.createPart(new MaterialImpl());
PartFactory factory = PartImpl::new; Part part = factory.createPart(new MaterialImpl()); 如果某類別有多個建構式,就會使用函式介面的方法簽署來比對,找出對應的建構式進行呼叫。 終於,〈認識 Lambda/Closure〉要告一段落了。「等一下!怎麼沒討論預設方法(Default method)?那不是 Lambda 專案的一部份嗎?」 是的,預設方法確實是 Lambda 專案的一部份,不過他跟將現在的 API 演化有關。預設方法解除了介面上的一些限制,讓 Java 介面在進行防禦式(Defensive)的 API 演化時容易一些,並為流程的重用開啟了更多可能性,不過,也帶入多重繼承上的一些複雜度。在討論如何將現在的 API 演化的時候,我們也許會看到一些函數式程式設計(Functional programming)的影子。我想,用一個新的系列來討論這些有趣的主題,會是比較好的做法,所以這些會留到下一個系列〈Java 開發者的函數式程式設計〉中來討論。敬請期待! |