允許在接口中有默認(rèn)方法實(shí)現(xiàn)
Java 8 允許我們使用default關(guān)鍵字,為接口聲明添加非抽象的方法實(shí)現(xiàn)。這個(gè)特性又被稱(chēng)為擴(kuò)展方法。下面是我們的第一個(gè)例子:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
在接口Formula中,除了抽象方法caculate以外,還定義了一個(gè)默認(rèn)方法sqrt。Formula的實(shí)現(xiàn)類(lèi)只需要實(shí)現(xiàn)抽象方法caculate就可以了。默認(rèn)方法sqrt可以直接使用。
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0
formula對(duì)象以匿名對(duì)象的形式實(shí)現(xiàn)了Formula接口。代碼很啰嗦:用了6行代碼才實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的計(jì)算功能:a*100開(kāi)平方根。我們?cè)谙乱还?jié)會(huì)看到,Java 8 還有一種更加優(yōu)美的方法,能夠?qū)崿F(xiàn)包含單個(gè)函數(shù)的對(duì)象。
Lambda表達(dá)式
讓我們從最簡(jiǎn)單的例子開(kāi)始,來(lái)學(xué)習(xí)如何對(duì)一個(gè)string列表進(jìn)行排序。我們首先使用Java 8之前的方法來(lái)實(shí)現(xiàn):
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
靜態(tài)工具方法Collections.sort接受一個(gè)list,和一個(gè)Comparator接口作為輸入?yún)?shù),Comparator的實(shí)現(xiàn)類(lèi)可以對(duì)輸入的list中的元素進(jìn)行比較。通常情況下,你可以直接用創(chuàng)建匿名Comparator對(duì)象,并把它作為參數(shù)傳遞給sort方法。
除了創(chuàng)建匿名對(duì)象以外,Java 8 還提供了一種更簡(jiǎn)潔的方式,Lambda表達(dá)式。
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
你可以看到,這段代碼就比之前的更加簡(jiǎn)短和易讀。但是,它還可以更加簡(jiǎn)短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
只要一行代碼,包含了方法體。你甚至可以連大括號(hào)對(duì){}和return關(guān)鍵字都省略不要。不過(guò)這還不是最短的寫(xiě)法:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java編譯器能夠自動(dòng)識(shí)別參數(shù)的類(lèi)型,所以你就可以省略掉類(lèi)型不寫(xiě)。讓我們?cè)偕钊氲匮芯恳幌耹ambda表達(dá)式的威力吧。
函數(shù)式接口
Lambda表達(dá)式如何匹配Java的類(lèi)型系統(tǒng)?每一個(gè)lambda都能夠通過(guò)一個(gè)特定的接口,與一個(gè)給定的類(lèi)型進(jìn)行匹配。一個(gè)所謂的函數(shù)式接口必須要有且僅有一個(gè)抽象方法聲明。每個(gè)與之對(duì)應(yīng)的lambda表達(dá)式必須要與抽象方法的聲明相匹配。由于默認(rèn)方法不是抽象的,因此你可以在你的函數(shù)式接口里任意添加默認(rèn)方法。
任意只包含一個(gè)抽象方法的接口,我們都可以用來(lái)做成lambda表達(dá)式。為了讓你定義的接口滿足要求,你應(yīng)當(dāng)在接口前加上@FunctionalInterface 標(biāo)注。編譯器會(huì)注意到這個(gè)標(biāo)注,如果你的接口中定義了第二個(gè)抽象方法的話,編譯器會(huì)拋出異常。
舉例:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
注意,如果你不寫(xiě)@FunctionalInterface 標(biāo)注,程序也是正確的。
方法和構(gòu)造函數(shù)引用
上面的代碼實(shí)例可以通過(guò)靜態(tài)方法引用,使之更加簡(jiǎn)潔:
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
Java 8 允許你通過(guò)::關(guān)鍵字獲取方法或者構(gòu)造函數(shù)的的引用。上面的例子就演示了如何引用一個(gè)靜態(tài)方法。而且,我們還可以對(duì)一個(gè)對(duì)象的方法進(jìn)行引用:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
讓我們看看如何使用::關(guān)鍵字引用構(gòu)造函數(shù)。首先我們定義一個(gè)示例bean,包含不同的構(gòu)造方法:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
接下來(lái),我們定義一個(gè)person工廠接口,用來(lái)創(chuàng)建新的person對(duì)象:
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
然后我們通過(guò)構(gòu)造函數(shù)引用來(lái)把所有東西拼到一起,而不是像以前一樣,通過(guò)手動(dòng)實(shí)現(xiàn)一個(gè)工廠來(lái)這么做。
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
我們通過(guò)Person::new來(lái)創(chuàng)建一個(gè)Person類(lèi)構(gòu)造函數(shù)的引用。Java編譯器會(huì)自動(dòng)地選擇合適的構(gòu)造函數(shù)來(lái)匹配PersonFactory.create函數(shù)的簽名,并選擇正確的構(gòu)造函數(shù)形式。