Java 8——Lambda表達(dá)式

參考文章地址

前言

在上一篇文章中踪旷,我們了解了利用行為參數(shù)化來傳遞代碼有助于應(yīng)對不斷變化的需求捻勉,它允許你定義一個代碼塊來表示一個行為,然后傳遞它桥爽。一般來說纳击,利用這個概念续扔,你就可以編寫更為靈活且可重復(fù)使用的代碼了。

但是你同時也看到焕数,使用匿名類來表示不同的行為并不令人滿意:代碼十分啰嗦纱昧,這會影響程序員在時間中使用行為參數(shù)化的積極性。Lambda表達(dá)式很好的解決了這個問題堡赔,它可以讓你很簡潔地表示一個行為或傳遞代碼∈洞啵現(xiàn)在你可以把Lambda表達(dá)式看作匿名功能,它基本上就是沒有聲明名稱的方法善已,但和匿名類一樣灼捂,它也可以作為參數(shù)傳遞給一個方法。

Lambda管中窺豹

可以把Lambda表達(dá)式理解為簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱换团,但它由參數(shù)列表悉稠、函數(shù)主體、返回類型艘包,可能還有一個拋出的異常列表的猛。

Lambda表達(dá)式鼓勵你采用上一篇文章中提到的行為參數(shù)化風(fēng)格,最終結(jié)果就是你的額代碼變得更加清晰辑甜、更加靈活衰絮。比如,利用Lambda表達(dá)式磷醋,你可以更為簡潔地自定義一個Comparator對象:


image.png

不得不承認(rèn),代碼看起來更清晰了胡诗。要是現(xiàn)在覺得Lambda表達(dá)式看起來一頭霧水的話也沒關(guān)系邓线,很快就會一點(diǎn)點(diǎn)的解釋清楚的。現(xiàn)在煌恢,請注意你基本上只傳遞了比較兩個蘋果重量所需要的代碼骇陈。看起來就像只傳遞了compare方法的主體瑰抵。你很快就會學(xué)到你雌,你甚至還可以進(jìn)一步簡化代碼。

為了進(jìn)一步說明,下面給出了Java 8五個有效的Lambda表達(dá)式的例子:


image.png

Java語言設(shè)計者選擇這樣的語法婿崭,是因為C#和Scala等語言中的類似功能廣受歡迎拨拓。Lambda的基本語法是:

  (parameters) -> expression

或(請注意語句的花括號
(parameters) -> { statements; }
你可以看到,Lambda表達(dá)式的語法很簡單氓栈,我們下來來測試一下你對這個模式的了解程度:

image.png

在哪里以及如何使用Lambda

現(xiàn)在你可能在想渣磷,在哪里可以使用Lambda表達(dá)式。直接公布答案:你可以在函數(shù)式接口上使用Lambda表達(dá)式授瘦。

函數(shù)式接口

還記得上一篇文章中醋界,為了參數(shù)化filter方法的行為而創(chuàng)建的Predicate<T>接口嗎?它就是一個函數(shù)式接口提完!為什么呢形纺?因為Predicate僅僅定義了一個抽象方法:

public interface Predicate<T>{
    boolean test(T t);
}

一言以蔽之,函數(shù)式接口就是之定義一個抽象方法的接口徒欣。你已經(jīng)知道了Java API中的一些其他函數(shù)式接口挡篓,如Comparator和Runnable

public interface Comparator<T>{
    int compare(T o1, T o2);
}

public interface Runnable{
    void run();
}

接口現(xiàn)在還可以擁有默認(rèn)方法(即在類沒有對方法進(jìn)行是現(xiàn)實時,其主體為方法提供默認(rèn)實現(xiàn)的方法帚称,如List的sort方法)官研。哪怕有很多默認(rèn)方法,只要接口只定義了一個抽象方法闯睹,它就仍然是一個函數(shù)式接口

為了檢測是否掌握了函數(shù)式接口的概念戏羽,我們來看一個小測試:

image

用函數(shù)式接口可以干什么呢?Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實現(xiàn)楼吃,并把整個表達(dá)式作為函數(shù)式接口的實例始花。這聽上去可能有些繞口,但是聯(lián)想到上一篇文章中的Lambda表達(dá)式改造的語句孩锡,或許就會清晰許多酷宵,它不同于使用匿名內(nèi)部類來完成時的笨拙,而是更加清晰直接:


image.png

你可能會想:“為什么只有在需要函數(shù)式接口的時候才可以傳遞Lambda呢躬窜?”語言的設(shè)計者也考慮過其他方法浇垦,例如給Java添加函數(shù)類型,但最終他們選擇了現(xiàn)在這種方式荣挨,因為這種方式自然且能避免語言變得更加復(fù)雜男韧。此外,大多數(shù)Java程序員都已經(jīng)熟悉了具有一個抽象方法的接口的理念(例如事件處理)默垄。

image.png

第一步:記得行為參數(shù)化

現(xiàn)在這段代碼時有局限的此虑。你只能讀文件的第一行。如果你想要返回頭兩行口锭,甚至返回使用最頻繁的詞朦前,該怎么辦呢?在理想的情況下,你要重用執(zhí)行設(shè)置和清理的代碼韭寸,并告訴processFile方法對文件執(zhí)行不同的操作春哨。這聽起來是不是很耳熟?是的棒仍,你需要把processFile的行為參數(shù)化悲靴。你需要一種方法把行為傳遞給processFile,以便它可以利用BufferedReader執(zhí)行不同的行為莫其。

傳遞行為正是Lambda的拿手好戲癞尚。那要是想一次讀兩行,這個新的processFile方法看起來又該是什么樣的呢乱陡?基本上浇揩,你需要一個接受BufferedReader并返回String的Lambda。例如憨颠,下面就是從BufferedReader中打印兩行的寫法:

String result = processFile((BufferedReader br) -> 
                                            br.readLine() + br.readLine());

第二步:使用函數(shù)式接口來傳遞行為

前面已經(jīng)解釋過了胳徽,Lambda僅可用于上下文是函數(shù)式接口的情況。你需要創(chuàng)建一個能匹配BufferedReader -> String爽彤,還可以拋出IOException異常的接口养盗。讓我們把這一接口叫做BufferedReaderProcessor吧。

@FunctionalInterface
public interface BufferedReaderProcessor{
    String process(BufferedReader b) throws IOException;
}

@FunctionalInterface 標(biāo)注表示該接口會設(shè)計成一個函數(shù)式接口适篙。如果你用此標(biāo)注定義了一個接口往核,而它卻不是函數(shù)式接口的話,編譯器將返回一個提示原因的錯誤嚷节。

現(xiàn)在你就可以把這個接口作為新的processFile方法的參數(shù)了:

public static String processFile(BufferedReaderProcessor p) throws IOException{
    ...
}

第三步:執(zhí)行一個行為

任何BufferedRader -> String形式的Lambda都可以作為參數(shù)來傳遞聂儒,因為它們符合BufferedReaderProcessor接口中定義的process方法的簽名。現(xiàn)在你只需要一種方法在processFile主體內(nèi)執(zhí)行Lambda所代表的代碼硫痰。請記住衩婚,Lambda表達(dá)式允許你直接內(nèi)聯(lián),為函數(shù)式接口的抽象方法提供實現(xiàn)效斑,并且將整個表達(dá)式作為函數(shù)式接口的一個實例非春。因此,你可以在processFile主體內(nèi)鳍悠,對得到的BufferedReaderProcessor對象調(diào)用process方法執(zhí)行處理:

public static String processFile(BufferedReaderProcesssor p) throws IOException{
    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
        return p.process(br);
    }
}

第四步:傳遞Lambda

現(xiàn)在你就可以通過傳遞不同的Lambda重用processFile方法税娜,并以不同的方式處理文件了:


image.png

下面的圖片總結(jié)了所采取的使processFile方法更加靈活的四個步驟:


image.png

使用函數(shù)式接口

如你所見的,函數(shù)式接口很有用藏研,因為抽象方法的簽名可以描述Lambda表達(dá)式的簽名。Java 8的庫設(shè)計師幫你在java.util.function包中引入了幾個新的函數(shù)式接口概行。

Predicate

java.util.function.Predicate<T>接口定義了一個名叫test的抽象方法蠢挡,它接受泛型T對象,并返回一個boolean。在你需要一個涉及類型T的布爾表達(dá)式時业踏,就可以使用這個接口:


image.png

Consumer

java.util.function.Consumer<T>定義了一個名叫accept的抽象方法禽炬,它接受泛型T的對象,沒有返回(void)勤家。你如果需要訪問類型T的對象腹尖,并對其執(zhí)行某些操作,就可以使用這個接口:


image.png

Function

java.util.function.Function<T,R>接口定義了一個叫做apply的方法伐脖,它接受一個泛型T的對象热幔,并返回一個泛型R的對象。如果你需要定義一個Lambda讼庇,將輸入對象的信息映射到輸出绎巨,就可以使用這個接口(比如提取蘋果的重量,或把字符串映射為它的長度):

image.png

還有更為豐富的一些函數(shù)式接口蠕啄,這里列舉了三個比較有代表性的场勤。

方法引用

方法引用讓你可以重復(fù)使用現(xiàn)有的方法定義,并像Lambda一樣傳遞它們歼跟。在一些情況下和媳,比起使用Lambda表達(dá)式,它們似乎更易讀哈街,感覺也更自然留瞳。下面就是借助Java 8API,用方法引用寫的一個排序的例子:

image.png

是不是更酷了叹卷?念起來就是“給庫存排序撼港,比較蘋果的重量”,這樣的代碼讀起來簡直就像是在描述問題本身骤竹,太酷了帝牡。

為什么要關(guān)心方法引用呢?方法引用可以被看作調(diào)用特定方法的Lambda的一種快捷寫法蒙揣。它的基本思想是靶溜,如果一個Lambda代表的知識“直接調(diào)用這個方法”,拿最好還是用名稱來調(diào)用它懒震,而不是去描述如何調(diào)用它罩息。

事實上,方法引用就是讓你根據(jù)已有的方法實現(xiàn)來創(chuàng)建Lambda表達(dá)式个扰,但是瓷炮,顯式地指明方法的名稱,你的代碼可讀性會更好递宅。

它是如何工作的呢娘香?當(dāng)你需要使用方法引用時苍狰,目標(biāo)引用放在分隔符** :: **前,方法的名稱放在后面烘绽。例如淋昭,Apple::getWeight就是引用了Apple類中定義的方法getWeight。請記住安接,不需要括號翔忽,因為你沒有實際調(diào)用這個方法,方法引用就是Lambda表達(dá)式(Apple a) -> a.getWeight()的快捷寫法盏檐。

下面給出一些在Java 8中方法引用的例子來讓你更加了解:

image.png

你可以把方法引用看作針對僅僅涉及單一方法的Lambda的語法糖歇式,因為你表達(dá)同樣的事情時寫的代碼更少了。

Lambda 和方法引用實戰(zhàn)

我們繼續(xù)來研究開始的那個問題——用不同的排序策略給一個Apple列表排序糯笙,并展示如何把一個原始粗暴的解決方案轉(zhuǎn)變得更為簡明:inventory.sort(comparing(Apple::getWeight));

第一步:傳遞代碼

很幸運(yùn)贬丛,Java 8的API已經(jīng)為你提供了一個List可用的sort方法,你不用自己去實現(xiàn)它给涕。那么最困難的部分已經(jīng)搞定了豺憔!但是,如何把排序的策略傳遞給sort方法呢够庙?你看恭应,sort方法的簽名是這樣的:

    void sort(Comparator<? super E> c)

它需要一個Comparator對象來比較兩個Apple!這就是在Java中傳遞策略的方式:它們必須包裹在一個對象里耘眨。我們說sort的行為被參數(shù)化了:傳遞給它的排序策略不同昼榛,其行為也會不同。

你的第一個解決方案看上去是這樣的:

  void sort(Comparator<? super E> c)

它需要一個Comparator對象來比較兩個Apple剔难!這就是在Java中傳遞策略的方式:它們必須包裹在一個對象里胆屿。我們說sort的行為被參數(shù)化了:傳遞給它的排序策略不同,其行為也會不同偶宫。

你的第一個解決方案看上去是這樣的:

public class AppleComparator implements Comparator<Apple>{
    public int compare(Apple a1, Apple a2){
        return a1.getWeigh().compareTo(a2.getWeight());
    }
}
inventory.sort(new AppleComparator());

第二步:使用匿名類

你可以使用匿名類來改進(jìn)解決方案非迹,而不是實現(xiàn)一個Comparator卻只實例化一次:

inventory.sort(new Comparator<Apple>(){
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

第三步:使用Lambda表達(dá)式

但你的解決方案仍然挺啰嗦的。使用Java 8引入的Lambda改進(jìn)后的代碼如下:

  inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

你的代碼還能變得更易讀一點(diǎn)嗎纯趋?Comparator具有一個叫做comparing的靜態(tài)輔助方法憎兽,它可以接受一個Function來提取Comparable鍵值,并生成一個Comparator對象吵冒。它可以像下面這樣用:
Comparator<Apple> c = Comparator.comparing((Apple a1) -> a.getWeight());
現(xiàn)在你可以把代碼再改得緊湊一點(diǎn)了:

import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));

第四步:使用方法引用

前面解釋過纯命,方法引用就是替代那些轉(zhuǎn)發(fā)參數(shù)的Lambda表達(dá)式的語法糖。你可以用方法引用讓你的代碼更加簡潔(假設(shè)你已經(jīng)靜態(tài)導(dǎo)入了java.util.Comparator.comparing):

inventory.sort(comparing(Apple::getWeight));

恭喜你痹栖,這就是你的最終解決方案亿汞!這筆Java 8之前的代碼好在哪兒呢?它比較短揪阿;它的意思也很明顯留夜,并且代碼讀起來和問題描述差不多:“對庫存進(jìn)行排序匙铡,比較蘋果的重量图甜“啵”

個人GitHub項目,記錄學(xué)習(xí)Java知識的過程 歡迎star

image.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黑毅,一起剝皮案震驚了整個濱河市嚼摩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矿瘦,老刑警劉巖枕面,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缚去,居然都是意外死亡潮秘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門易结,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枕荞,“玉大人,你說我怎么就攤上這事搞动□锞” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵鹦肿,是天一觀的道長矗烛。 經(jīng)常有香客問我,道長箩溃,這世上最難降的妖魔是什么瞭吃? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮涣旨,結(jié)果婚禮上歪架,老公的妹妹穿的比我還像新娘。我一直安慰自己开泽,他們只是感情好牡拇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著穆律,像睡著了一般惠呼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上峦耘,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天剔蹋,我揣著相機(jī)與錄音,去河邊找鬼辅髓。 笑死泣崩,一個胖子當(dāng)著我的面吹牛少梁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矫付,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凯沪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了买优?” 一聲冷哼從身側(cè)響起妨马,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杀赢,沒想到半個月后烘跺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脂崔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年滤淳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砌左。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡脖咐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绊困,到底是詐尸還是另有隱情文搂,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布秤朗,位于F島的核電站煤蹭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏取视。R本人自食惡果不足惜硝皂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望作谭。 院中可真熱鬧稽物,春花似錦、人聲如沸折欠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锐秦。三九已至咪奖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酱床,已是汗流浹背羊赵。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扇谣,地道東北人昧捷。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓闲昭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親靡挥。 傳聞我的和親對象是個殘疾皇子序矩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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