一直想寫一套技術(shù)的文章,苦于不知從何落筆。今天得空從忙碌的敲代碼中抽身出來,就從Java8開始說起吧。
在我看來,Java至今有兩個翻天覆地的版本,一個是5,另一個就是8。Java8發(fā)布于2014年,至今已經(jīng)四年多了,伴隨著9和10的發(fā)布,8也不能算是多新的版本了。那么Java8的十大“新”特性如今也應(yīng)該是各個程序員基本技能的一部分了。時至今日,很多軟件也紛紛把jdk的最低要求設(shè)置到了8,程序員們也沒有任何理由使用低于jdk8的java進行開發(fā)了。
那么,Java8提供了哪些特性,可以稱得上“翻天覆地”呢?讓我們一起來看一下吧。
首先,最最主要的,就是從語言層面引入了函數(shù)式編程的思想。很多腳本語言對函數(shù)式編程有著很好的支持,比如javascript和python,函數(shù)(Java中叫方法,以下可以將函數(shù)和方法作為同義詞)可以像其他類型的數(shù)據(jù)一樣作為函數(shù)的參數(shù)或者返回值,這樣我們可以輕松地實現(xiàn)策略模式。比如在自定義排序函數(shù)中,我們可以將排序的策略作為一個參數(shù)傳遞給負(fù)責(zé)排序的函數(shù)。在Java8之前,Java對函數(shù)式編程的支持就顯得十分笨重了。由于Java的方法都依賴于類,因此想要將方法作為參數(shù)或者返回值,必須用類將方法進行包裝(wrap),然后將類的對象作為參數(shù)或者返回值進行傳遞。這樣的實現(xiàn)無論從語法書寫上,還是從閱讀上,都顯得不那么優(yōu)雅。因此,Java的語言設(shè)計師們決定引入對函數(shù)式編程更優(yōu)雅的支持。那么,過程中,設(shè)計師們做了很多考量,比如像其他語言一樣,引入函數(shù)這樣的數(shù)據(jù)類型。但是,受到兼容歷史版本等限制的影響,最終設(shè)計師們采用了一種比較折衷的方法,這就是我們所說的lambda表達(dá)式。
下面隆重有請我們今天的主角——lambda表達(dá)式登場。lambda表達(dá)式是Java8引入的一項新語法,用->符號實現(xiàn)對函數(shù)式編程的支持。第一眼看到lambda表達(dá)式時,我覺得Java不那么像Java了,但是經(jīng)過一段時間的使用體會,我感受到了這一項新語法引入的強大之處,也逐漸接受了Java大軍中的這樣一個新伙伴。為什么叫l(wèi)ambda表達(dá)式呢?lambda是希臘字母λ的英文拼寫,在各個語言中,均有代表匿名函數(shù)之意。Java中也沿用了這一定義方式,將新的語法稱為lambda表達(dá)式。lambda表達(dá)式的書寫很簡單,如:
x, y -> x + y
() -> doSomeThing()
(int x, String s) -> x < s.length()
上述表達(dá)式表達(dá)的含義可就沒那么簡單了。它其實是一種語法糖,代表一個被接口包裝的方法。如上面的第一行代碼,表達(dá)的是一個二元求和的方法。它大致相當(dāng)于:
interface Calc {
int sum(int x, int y)
}
class CalcImpl implements Calc{
int sum(int x, int y){
return x + y;
}
}
但是在lambda表達(dá)式中,x和y的類型是根據(jù)實際情況進行推斷的。當(dāng)然也可以顯式的指定x和y的類型。
看到這里各位可能有點蒙。沒關(guān)系,上面只是為了讓大家看到lambda表達(dá)式語法上的簡潔性。下面我們具體來說一說lambda表達(dá)式是怎么一回事。我們還是從上面排序的例子說起。比如現(xiàn)在有一個字符串?dāng)?shù)組:
{"abc", "abcde", "abbb", ....}
我們要對數(shù)組進行自定義排序,比如按照字符串長度進行排序。我們知道Java提供了Arrays.sort方法進行數(shù)組排序,重載方法中有一個方法傳遞了一個Comparator接口的對象用于實現(xiàn)自定義排序,在Java8以前,我們會這么寫:
String[] array = {"abc", "abcde", "abbb", "bbccd"};
Arrays.sort(array, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
即使用一個匿名內(nèi)部類來實現(xiàn)Comparator接口。(不熟悉這一段代碼的童鞋可以回去翻一翻Java基礎(chǔ)哦)。引入lambda表達(dá)式以后,我們可以這么寫:
String[] array = {"abc", "abcde", "abbb", "bbccd"};
Arrays.sort(array, (o1, o2) -> o1.length() - o2.length());
可以看到,lambda表達(dá)式用一行代碼代替了上面六行代碼。lambda表達(dá)式由參數(shù)列表,箭頭符號(->)和方法體三部分組成。參數(shù)列表與接口的compare方法參數(shù)列表相同,類型可以自動推斷,方法體代表對這個方法的實現(xiàn)。方法體使用{}包圍,如果只有一行代碼,{}可以省略。
為了進一步理解lambda表達(dá)式的原理,這里要先引用函數(shù)式接口的概念。我們上面提到了,Java的方法依賴于類,這一點即使在Java8中也沒有改變。因此想要傳遞一個方法作為參數(shù)或者返回值,我們還是要傳遞一個類(或者接口)的對象。函數(shù)式接口就是這樣的接口,它的內(nèi)部只有一個未實現(xiàn)的抽象方法,也就是說我們使用這個接口其實就是為了使用這個未實現(xiàn)的方法,然后對它進行實現(xiàn)。例如:
@FunctionalInterface
interface Wrapper{
void method(int param);
}
Wrapper接口的唯一作用就是包裝method方法,將其作為使用方法的參數(shù)或者返回值進行傳遞。上面的@FunctionalInterface注解是可選的,它可以在編譯階段檢查接口是否符合函數(shù)式接口的要求,如果不符合,則會編譯失敗。
現(xiàn)在我們可以清晰地看到,從本質(zhì)上講,lambda表達(dá)式就是函數(shù)式接口的匿名實現(xiàn)類。凡是參數(shù)中傳遞函數(shù)式接口對象的地方,我們都可以使用lambda表達(dá)式進行替換。例如線程的創(chuàng)建:
// 使用匿名內(nèi)部類創(chuàng)建線程
Runnable runnable = new Runnable() {
@Override
public void run() {
doSomeThing();
}
};
new Thread(runnable).start();
// 使用lambda表達(dá)式創(chuàng)建線程
Runnable runnable = () -> doSomeThing();
new Thread(runnable).start();
再如,集合的遍歷:
/*
* Collection forEach循環(huán)
*/
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 8, 7, 6, 5);
// fori循環(huán)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// foreach循環(huán)
for (int ele : list) {
System.out.println(ele);
}
// lambda表達(dá)式
list.forEach(ele -> System.out.println(ele));
好了,先到這里吧。下一篇我們將分析lambda表達(dá)式的更多使用場景,以及如何使用方法引用進一步簡化lambda表達(dá)式。