Java SE基礎(chǔ)鞏固(十五):lambda表達(dá)式

1 概述

Java8據(jù)說是Java誕生以來最大的一次演進(jìn)酝掩,說實(shí)話问潭,對我個(gè)人來說沒有什么特別大的感受,因?yàn)槲覍W(xué)Java也就最近一兩年的事干跛,Java8在2014年3月18日發(fā)布,新增的特性確實(shí)非常驚艷祟绊,在語言特性層面上新增了lambda楼入,Optional,默認(rèn)方法牧抽,Stream API等嘉熊,在虛擬機(jī)層面上新增了G1收集器(不過在Java9之后才改為默認(rèn)的垃圾收集器)......

我個(gè)人認(rèn)為Java8和語言相關(guān)的幾個(gè)最重要的特性是如下幾個(gè):

  • lambda表達(dá)式和方法引用(其實(shí)是lambda表達(dá)式的一種特例)
  • Stream API
  • 接口的默認(rèn)方法
  • Optinal
  • CompletableFuture

本系列文章的后面幾篇文章會圍繞這幾個(gè)主題來展開,今天就先上個(gè)開胃菜扬舒,lambda表達(dá)式阐肤!

2 什么是lambda表達(dá)式

lambda表達(dá)式也叫做匿名函數(shù),其基于著名的λ演算得名,關(guān)于λ演算孕惜,推薦大家去找找關(guān)于“丘奇數(shù)”相關(guān)的資料愧薛。Java一直被人詬病的一點(diǎn)就是“啰嗦”,通常為了實(shí)現(xiàn)一個(gè)小功能衫画,就不得不編寫大量的代碼毫炉,而用其他的語言例如Python等,也許寥寥幾行代碼就解決了削罩,但支持lambda表達(dá)式之后碘箍,這一情況得到了大大的改善,現(xiàn)在只要使用得當(dāng)鲸郊,可以大大縮減代碼里丰榴,使代碼的目的更加清晰,易讀秆撮,純粹四濒。

在Java中,很多時(shí)候在使用一些API的時(shí)候职辨,必須要給出一些接口的實(shí)現(xiàn)盗蟆,但因?yàn)樵搶?shí)現(xiàn)其實(shí)也就用一次,專門去創(chuàng)建一個(gè)新的實(shí)現(xiàn)類并不劃算舒裤,所以一般大多數(shù)人采取的措施應(yīng)該是創(chuàng)建一個(gè)匿名實(shí)現(xiàn)類喳资,比較典型就是Collections.sort(List<T> list, Comparator<? super T> c)方法,該方法接受一個(gè)Comparator類型的參數(shù)腾供,Comparator是一個(gè)接口仆邓,表示“比較器”,如果要使用該方法對集合元素進(jìn)行排序伴鳖,就必須提供一個(gè)Comparator接口的實(shí)現(xiàn)节值,否則無法通過編譯。如下所示:

Collections.sort(numbers, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

其實(shí)這個(gè)實(shí)現(xiàn)類的核心只有一行榜聂,即return o1.compareTo(o2);但我們卻不得不編寫其他“啰嗦”的代碼搞疗,如果使用lambda表達(dá)式,會是怎么個(gè)樣子呢须肆?

Collections.sort(numbers, (n1, n2) -> n1.compareTo(n2));

沒錯(cuò)匿乃,就是那么簡單粗暴,就是一行核心代碼豌汇。其他的比如方法簽名啥的統(tǒng)統(tǒng)可以省略了幢炸,不僅簡潔,而且語義也更加清晰瘤礁,讀起來就好像是說:“sort方法阳懂,幫我吧numbers這個(gè)序列排個(gè)序梅尤,排序規(guī)則就按照n1.compareTo(n2)的返回值來決定”」袼迹現(xiàn)在岩调,是不是感覺,寫代碼就像在和計(jì)算機(jī)對話一樣簡單赡盘?但(n1, n2) -> n1.compareTo(n2)這玩意是個(gè)什么鬼号枕?還帶個(gè)箭頭?不用著急陨享,下面馬上介紹lambda表達(dá)式的語法葱淳。

2.1 lambda表達(dá)式的語法

  • 第一部分是lambda的參數(shù)列表,因?yàn)镃omparator.compare()方法接受兩個(gè)參數(shù)抛姑,所以這里給出兩個(gè)參數(shù)n1和n2赞厕,可以省略具體的類型,Java編譯器會自動推斷定硝。
  • 第二部分是箭頭皿桑,沒什么特殊的地方,只是Java語言覺得使用這個(gè)蔬啡,各個(gè)語言的實(shí)現(xiàn)也不太一樣诲侮,例如Python是:號,簡單理解的就當(dāng)是把參數(shù)列表和函數(shù)主體分開的東西吧箱蟆。
  • 第三部分就是函數(shù)主體沟绪,也就是真正執(zhí)行邏輯的地方。

如果函數(shù)主體僅僅包含一行代碼空猜,可以省略花括號{}和return關(guān)鍵字(如果有的話)绽慈。對于我們的例子,可以改寫成這樣:

Collections.sort(numbers, (n1, n2) -> {return n1.compareTo(n2);});

注意分號辈毯!因?yàn)榇藭r(shí)return n1.compareTo(n2);就是一條普通的Java語句了久信,必須遵守Java的語法規(guī)則。好了漓摩,盡管我們現(xiàn)在明白了lambda語句的語法規(guī)則裙士,但還有一個(gè)關(guān)鍵的問題,就是為什么要這樣寫管毙,換句話說腿椎,為什么要有倆參數(shù),這return又是幾個(gè)意思夭咬?還有到底哪里才可以使用lambda表達(dá)式啃炸?說到這,就不得不說一下和lambda息息相關(guān)的東西了:函數(shù)式接口卓舵。

3 函數(shù)式接口

函數(shù)式接口是這樣的:只有一個(gè)抽象方法的接口就是函數(shù)式接口南用。為什么要特別強(qiáng)調(diào)抽象方法呢?Java接口里聲明的方法不都是抽象方法嗎?在Java8之前裹虫,這么說確實(shí)沒有任何問題肿嘲,但Java8新增了接口的默認(rèn)方法,可以在接口里給出方法的具體實(shí)現(xiàn)筑公,這里先不多說雳窟,后面的文章會詳細(xì)討論這個(gè)東西。

lambda表達(dá)式僅可以用在函數(shù)式接口上匣屡,我們在上面遇到的Comparator就是一個(gè)函數(shù)式接口封救,他只有一個(gè)抽象方法:compare(),其方法簽名是這樣的:

int compare(T o1, T o2);

現(xiàn)在來看看 (n1, n2) -> n1.compareTo(n2)這個(gè)表達(dá)式捣作,是不是發(fā)現(xiàn)了什么誉结?沒錯(cuò),其實(shí)lambda表達(dá)式的參數(shù)列表就是對應(yīng)的函數(shù)式接口的抽象方法的參數(shù)列表券躁,并且類型可以省略(編譯器自動推斷)搓彻,然后n1.compareTo(n2)的返回值是int類型,也符合compare()的方法描述嘱朽。這樣就算是把lambda表達(dá)式和接口的抽象方法簽名匹配成功了旭贬,不會出現(xiàn)編譯錯(cuò)誤。

除此之外搪泳,Runnable也是一個(gè)函數(shù)式接口稀轨,它只有一個(gè)抽象方法,即run()岸军,run()方法的方法簽名如下所示:

public abstract void run();

不接受任何參數(shù)奋刽,也沒有返回值。那如果要編寫對應(yīng)的lambda表達(dá)式艰赞,該如何做呢佣谐?其實(shí)非常簡單,下面是一個(gè)示例:

Runnable r = () -> {
    System.out.println(Thread.currentThread().getName());
    //do something
};

如果觀察仔細(xì)的話方妖,會發(fā)現(xiàn)狭魂,示例代碼中把這個(gè)lambda表達(dá)式賦值給了Runnable類型的變量r!經(jīng)過上面的討論党觅,我們知道雌澄,其實(shí)lambda就是一個(gè)方法實(shí)現(xiàn)(其實(shí)叫做函數(shù)會更加合適),這條賦值語句看起來就好像是再說:“把方法(函數(shù))賦值給變量杯瞻!”镐牺。如果沒有接觸過函數(shù)式編程,會覺得這樣很奇怪魁莉,怎么能把方法賦值給變量呢睬涧?計(jì)算機(jī)就是這樣有意思募胃,總是有各種各樣奇奇怪怪的東西沖擊我們的思維!那這有什么用呢畦浓?咱先不說什么高階函數(shù)痹束,科里化啥的(這些是函數(shù)式編程里的概念),就說一點(diǎn):意味著我們可以把方法(函數(shù))當(dāng)做變量來使用宅粥!即現(xiàn)在方法就是Java世界里的“一等公民”了参袱!既可以將其作為參數(shù)傳遞給其他方法(函數(shù))电谣,還可以將其作為其他方法(函數(shù))的返回值(以后會講到具體的案例)

4 策略模式

策略模式是著名的23種設(shè)計(jì)模式中的一種秽梅,關(guān)于它的描述,我這里就不多說了剿牺。直接來看個(gè)例子吧企垦。

例子是這樣的,現(xiàn)在有一個(gè)代表汽車的Car類以及一個(gè)Car列表晒来,現(xiàn)在我們想要篩選列表中符合要求的汽車钞诡,為了應(yīng)對多變的篩選方法,我們打算用策略模式來實(shí)現(xiàn)功能湃崩。

下面是Car類的代碼:

public class Car {

    //品牌
    private String brand;

    //顏色
    private Color color;

    //車齡
    private Integer age;
    
    //三個(gè)參數(shù)的構(gòu)造函數(shù)以及setter和getter
    
    //顏色的枚舉
    public enum Color {
        RED,WHITE,PINK,BLACK,BLUE;
    }
}

//包含Car對象的列表
List<Car> cars = Arrays.asList(
        new Car("BWM",Car.Color.BLACK, 2),
        new Car("Tesla", Car.Color.WHITE, 1),
        new Car("BENZ", Car.Color.RED, 3),
        new Car("Maserati", Car.Color.BLACK,1),
        new Car("Audi", Car.Color.PINK, 5));

我們希望用一個(gè)方法來封裝篩選的邏輯荧降,其方法簽名偽代碼如下所示:

cars carFilter(cars, filterStrategy);

接下來實(shí)現(xiàn)策略模式,下面是相關(guān)的代碼:

public interface CarFilterStrategy {
    boolean filter(Car car);
}

public class BWMCarFilterStrategy implements CarFilterStrategy {
    @Override
    public boolean filter(Car car) {
        return "BWM".equals(car.getBrand());
    }
}

public class RedColorCarFilterStrategy implements CarFilterStrategy {

    @Override
    public boolean filter(Car car) {
        return Car.Color.RED.equals(car.getColor());
    }
}

為了簡單攒读,僅僅實(shí)現(xiàn)了兩種篩選策略朵诫,第一種是刪選出品牌是“BWM”的汽車,第二種是刪選出顏色為紅色的汽車薄扁。最后來實(shí)現(xiàn)carFilter方法剪返,如下所示:

private static List<Car> carFilter(List<Car> cars, CarFilterStrategy strategy) {
    List<Car> filteredCars = new ArrayList<>();
    for (Car car : cars) {
        if (strategy.filter(car)) {
            filteredCars.add(car);
        }
    }
    return filteredCars;
}

最后的最后是測試代碼:

public static void main(String[] args) {
    System.out.println(carFilter(cars, new BWMCarFilterStrategy()));
    System.out.println("----------------------------------------");
    System.out.println(carFilter(cars, new RedColorCarFilterStrategy()));
}

分別實(shí)例化兩個(gè)策略,將其作為參數(shù)傳遞給carFilter()方法邓梅,最終的輸出如下所示:

[Car{brand='BWM', color=BLACK, age=2}]
----------------------------------------
[Car{brand='BENZ', color=RED, age=3}]

確實(shí)符合預(yù)期脱盲。是不是就到此為止了呢?當(dāng)然不日缨!我們發(fā)現(xiàn)钱反,其實(shí)BWMCarFilterStrategy以及RedColorCarFilterStrategy的實(shí)現(xiàn)代碼都非常簡單,僅僅寥寥幾行代碼匣距,而且CarFilterStrategy接口僅僅有一個(gè)filter抽象方法诈铛,顯然是一個(gè)函數(shù)式接口,那我們能不能用lambda表達(dá)式來簡化呢墨礁?答案是:完全可以幢竹!而且更加推薦用lambda表達(dá)式來簡化這種情況。

4.1 用lambda表達(dá)式來簡化代碼

只要略微做一些修改就行了:

System.out.println(carFilter(cars, car -> "BWM".equals(car.getBrand())));
System.out.println("----------------------------------------");
System.out.println(carFilter(cars, car -> Car.Color.RED.equals(car.getColor())));

這里不再使用BWMCarFilterStrategy以及RedColorCarFilterStrategy兩個(gè)類了恩静,直接用lambda表達(dá)式就行了焕毫!最后把這倆實(shí)現(xiàn)刪除掉蹲坷!是不是頓時(shí)感覺整個(gè)項(xiàng)目的代碼清爽了許多?

4.2 需要注意的

其實(shí)本小節(jié)的例子有些過于特殊了邑飒,如果你項(xiàng)目中的策略模式的實(shí)現(xiàn)非常復(fù)雜循签,其策略不是簡簡單單的幾行代碼就能解決的,此時(shí)要么進(jìn)一步封裝代碼疙咸,要么就最好不要用lambda表達(dá)式了县匠,因?yàn)槿绻壿嫃?fù)雜的話,強(qiáng)行使用lambda不僅僅不能簡化代碼撒轮,反而會使得代碼更加晦澀乞旦。

5 方法引用

最后簡單講一下方法引用吧,方法引用其實(shí)是lambda表達(dá)式的一種特殊情況的表示题山,語法規(guī)則是:

<class name or instance name>:<method name>

如果lambda表達(dá)式的主體邏輯僅僅是一個(gè)調(diào)用方法的語句的話兰粉,那么就可以將其轉(zhuǎn)換為方法引用,如下所示:

//普通的lambda表達(dá)式
numbers.forEach(n -> System.out.println(n));
//轉(zhuǎn)換成方法引用
numbers.forEach(System.out::println);

他倆效果是完全一樣的顶瞳,但顯然方法引用更加簡潔玖姑,語義也更加明確了,這一語法糖“真香慨菱!”焰络。具體的我就不多說了,建議看看《Java8 實(shí)戰(zhàn)》一書符喝,里面有非常非常詳細(xì)的介紹闪彼。

6 小結(jié)

本文簡單介紹了lambda表達(dá)式的語法以及使用。lambda表達(dá)式確實(shí)能大大簡化原本復(fù)雜啰嗦的Java代碼洲劣,而且更加靈活备蚓,語義也更加清晰明了,寫代碼的時(shí)候就好像用自然語言和計(jì)算機(jī)對話一樣囱稽!但也不是哪里都能使用的郊尝,一個(gè)最基本的要求就是:其放置的位置要對應(yīng)著一個(gè)函數(shù)式接口。函數(shù)式接口即只有一個(gè)抽象方法的接口战惊,例如Comparator流昏,Runnable等。除此之外吞获,使用lambda表達(dá)式的時(shí)候况凉,其主體邏輯最好不要超過10行,否則最好還是換一種方式來實(shí)現(xiàn)各拷,這里10行并不是那么嚴(yán)格刁绒,具體情況還要具體分析。方法引用是一種特殊情況下的lambda表達(dá)式的表示方法烤黍,可以理解為是lambda的一個(gè)語法糖知市,其語義更加明確傻盟,語法也更加簡潔,用起來還是非常舒服的嫂丙!

最后娘赴,作為一個(gè)補(bǔ)充,來簡單看看JDK內(nèi)置的一些通用性比較強(qiáng)的函數(shù)式接口跟啤,這些接口都在java.util.function包下诽表,我沒數(shù)過,咋一看估計(jì)得有40多個(gè)吧隅肥。常用的有Function竿奏,Predicate,Consumer武福,Supplier等议双。Function的抽象方法的方法簽名如下所示:

R apply(T t); //T,R是泛型

簡單從語義上來看痘番,就是傳入一個(gè)T類型的值捉片,然后apply函數(shù)將其轉(zhuǎn)換成R類型的值,即一對一映射汞舱。其他的接口就不做介紹了伍纫。

7 參考資料

《Java8 實(shí)戰(zhàn)》

阿隆佐.丘奇的天才之作——lambda演算中的數(shù)字

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市昂芜,隨后出現(xiàn)的幾起案子莹规,更是在濱河造成了極大的恐慌,老刑警劉巖泌神,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件良漱,死亡現(xiàn)場離奇詭異,居然都是意外死亡欢际,警方通過查閱死者的電腦和手機(jī)母市,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來损趋,“玉大人患久,你說我怎么就攤上這事』氩郏” “怎么了蒋失?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桐玻。 經(jīng)常有香客問我篙挽,道長,這世上最難降的妖魔是什么镊靴? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任铣卡,我火速辦了婚禮观腊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘算行。我一直安慰自己梧油,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布州邢。 她就那樣靜靜地躺著儡陨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪量淌。 梳的紋絲不亂的頭發(fā)上骗村,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音呀枢,去河邊找鬼胚股。 笑死,一個(gè)胖子當(dāng)著我的面吹牛裙秋,可吹牛的內(nèi)容都是我干的琅拌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摘刑,長吁一口氣:“原來是場噩夢啊……” “哼进宝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枷恕,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤党晋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后徐块,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體未玻,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年胡控,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扳剿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铜犬,死狀恐怖舞终,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癣猾,我是刑警寧澤敛劝,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站纷宇,受9級特大地震影響夸盟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜像捶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一上陕、第九天 我趴在偏房一處隱蔽的房頂上張望桩砰。 院中可真熱鬧,春花似錦释簿、人聲如沸亚隅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煮纵。三九已至,卻和暖如春偏螺,著一層夾襖步出監(jiān)牢的瞬間行疏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工套像, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酿联,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓夺巩,卻偏偏與公主長得像贞让,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子劲够,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容