當我們臨時想要為函式介面定義實作時,Lambda表示式確實是很方便,然而有時候,你會發現某些靜態方法的本體實作流程,與你自行定義的Lambda表示式根本就是相同,JDK8考慮到這種狀況,Lambda表示式只是定義函式介面實作的一種方式,除此之外,只要靜態方法的方法簽署中參數與傳回值定義相同,也可以使用靜態方法來定義函式介面實作。
舉例來說,在 匿名類別與 Lambda 中曾定義過以下程式碼:
package cc.openhome;
public class StringOrder {
public static int byLength(String s1, String s2) {
return s1.length() - s2.length();
}
}
如果想要定義Comparator<String>
的實作,必須實作其定義的int compare(String s1, String s2)
方法,你可以使用Lambda表示式定義:
Comparator<String> lengthComparator = (s1, s2) -> s1.length() - s2.length();
然而仔細觀察,除了方法名稱之外,StringOrder
的靜態方法byLength
之參數、傳回值,與Comparator<String>
的int compare(String s1, String s2)
之參數、傳回值都相同,你可以讓函式介面的實作參考StringOrder
的靜態方法byLength
:
Comparator<String> lengthComparator = StringOrder::byLength;
這樣的特性在JDK8中稱為方法參考(Method references),這可以讓你避免到處寫下Lambda表示式,儘量運用現有的API實作,也可以改善可讀性,在 匿名類別與 Lambda 中就探討過,與其寫下 …
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, (name1, name2) -> name1.length() - name2.length());
不如寫下以下來得清楚:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, StringOrder::byLength);
除了參考靜態方法作為函式介面實作之外,還可以參考特定物件的實例方法。例如,假設你正在設計一個可以過濾職缺應徵者的軟體,而你有以下類別:
public class JobVacancy {
...
public int bySeniority(JobApplicant ja1, JobApplicant ja2) {
...
}
}
如果你使用JDK8,並如下撰寫Lambda演算式來進行應徵者的排序:
JobVacancy vacancy = createJobVacancy(...);
JobApplicant[] applicants = retrieveApplicants(...);
Arrays.sort(applicants, (ja1, ja2) -> vacancy.bySeniority(ja1, ja2));
Lambda表示式捕捉了vacancy
參考的物件,實際上,bySeniority
方法的簽署與Comparator<JobApplicant>
的compare
方法相同,此時,我們可以直接參考vacancy
物件的bySeniority
方法:
Arrays.sort(applicants, vacancy::bySeniority);
另一個更簡單的例子可以在 Iterable 與 Iterator 中看到,JDK8在Iterable
上新增了forEach()
方法,可以讓你迭代物件進行指定處理:
List<String> names = Arrays.asList("Justin", "Monica", "Irene");
names.forEach(name -> out.println(name));
new HashSet(names).forEach(name -> out.println(name));
new ArrayDeque(names).forEach(name -> out.println(name));
發現了嗎?寫了三個重複的Lambda表示式,依照 方法與建構式參考 的說明,你可以直接參考out
的println()
方法:
List<String> names = Arrays.asList("Justin", "Monica", "Irene");
names.forEach(out::println);
new HashSet(names).forEach(out::println);
new ArrayDeque(names).forEach(out::println);
函式介面實作也可以參考類別上定義的非靜態方法,函式介面會試圖用第一個參數方法接收者,而之後的參數依序作為被參考的非靜態方法之參數。舉例而言:
Comparator<String> naturalOrder = String::compareTo;
雖然Comparator<String>
的int compare(String s1, String s2)
方法必須有兩個參數,然而在以上的方法參考中,會試圖用第一個參數s1
作為compareTo()
的方法接收者,而之後的參數只剩下s2
,剛好作為s1.compareTo(s2)
,實際的應用在 匿名類別與 Lambda 中也看過:
String[] names = {"Justin", "caterpillar", "Bush"};
Arrays.sort(names, String::compareTo);
...
Arrays.sort(names, String::compareToIgnoreCase);
方法參考用來重用現有API的方法流程,而JDK8還提供了建構式參考(Constructor references),用來重用現有API的物件建構流程。你也許會發出疑問:「建構式?他們有傳回值型態嗎?」語法上不需要,但事實上有!其實每個建構式都會有傳回值型態,也就是定義他們的類別本身。例如,如果你有個介面如下定義:
package cc.openhome;
public interface Function {
R apply(P p);
}
如果你使用 定義與使用泛型 中的ArrayList
,定義了一個map()
方法,可以將一個List
中的實例轉換為另一個型態的實例:
static <X, Y> ArrayList<Y> map(ArrayList<X> list, Function<X, Y> mapper) {
ArrayList<Y> mappedList = new ArrayList<>();
for(int i = 0; i < list.size(); i++) {
mappedList.add(mapper.apply(list.get(i)));
}
return mappedList;
}
你也許會這麼使用這個map()
方法:
ArrayList<String> names = new ArrayList<>();
...
ArrayList<Person> persons = map(names, name -> new Person(name));
實際上,你不過是將name
用來呼叫Person
的建構式,那麼不如直接參考Person
的建構式:
ArrayList<String> names = new ArrayList<>();
...
ArrayList<Person> persons = map(names, Person::new);