就從Java8開始吧(一)lambda表達(dá)式詳解

一直想寫一套技術(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á)式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容