Java8 實戰(zhàn)學(xué)習(xí) — Lambda 表達式
上一章趾代,我們學(xué)習(xí)了參數(shù)化代碼的實現(xiàn)方法辜腺,這個邏輯的推導(dǎo)對我自己來說還是蠻有意義的宣鄙,因為這將對我以后的代碼編輯產(chǎn)生影響番电。
這一節(jié)我們繼續(xù)學(xué)習(xí)岗屏,我們將學(xué)習(xí) Lambda 表達式的具體使用辆琅。
Lambda 概述:
可以把Lambda表達式理解為簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它 有參數(shù)列表这刷、函數(shù)主體婉烟、返回類型,可能還有一個可以拋出的異常列表暇屋。這個定義夠大的似袁,讓我 們慢慢道來。
Lambda 的主要特點就是需要我們寫的東西很少咐刨,但是構(gòu)建 Lambda 的過程是需要思考的昙衅,以前看到一個接口或者一個抽象類,我們第一反應(yīng)是 new Function(){...}
定鸟,但是有了 Lambda 以后我們需要重新思考而涉,以便于我們代碼更加便于閱讀和減少代碼的書寫。
Lambda 的標準形式:
書中通過構(gòu)建一個 Comparator 對象來給我們舉例說明如果替換原來的匿名內(nèi)部類寫法联予。 我們都知道實現(xiàn)一個 Comparator 的方法:
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
};
我們可以使用 Comparator 的方法為:
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
這看起來并沒有難度啼县,閱讀起來是 我有兩個蘋果對象,拿參數(shù) a1,a2 作為比較對象,第一個蘋果的重量和第二個蘋果的重量比較沸久,并返回比較結(jié)果(0代表相同谭羔,大于0的數(shù)代表a1重量大于a2重量,小于0則相反)麦向。
請注意你基本上只傳遞了比較兩個蘋果重量所真 正需要的代碼】筒妫看起來就像是只傳遞了compare方法的主體诵竭。
書中給出了上述解釋,之后給出語法通用表示:
- (parameters) -> expression 即 (參數(shù))-> 方法實現(xiàn) 適合單個語句
- (parameters) -> { statements; } (參數(shù))-> {方法實現(xiàn);} 適合多語句
注意:
使用第一種方式不可出現(xiàn)
兼搏;
若方法實現(xiàn)即 Lambda 主體如果有多個語句卵慰,就必須使用{;}
-
書中
(String s) -> s.length()
這個例子的后邊說了一句話當時對我造成了不少困惑:第一個Lambda表達式具有一個String類型的參 數(shù)并返回一個 int 。 Lambda沒有 return 語句佛呻, 因為已經(jīng)隱含了return
這說明的意思說 如果是第一種表達方式或者第二種表達方式的返回void 都屬于 return 類型可以推敲的時候裳朋,可以不寫 return ,并不能說 Lambda 沒有 return吓著。
哪里可以使用 Lambda
Lambda表達式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實現(xiàn)鲤嫡,并把 整個表達式作為函數(shù)式接口的實例 (具體說來,是函數(shù)式接口一個具體實現(xiàn) 的實例)绑莺。你用匿名內(nèi)部類也可以完成同樣的事情暖眼,只不過比較笨拙:需要提供一個實現(xiàn),然后 再直接內(nèi)聯(lián)將它實例化纺裁。
這里有兩個概念:
- 函數(shù)式接口:函數(shù)式接口就是只定義一個抽象方法的接口
- Lambda 是函數(shù)式接口的抽象方法的實現(xiàn)诫肠,但它可以作為整個接口的一個具體實例司澎。
厲害了我的哥,第二點難理解栋豫,但這又恰恰是讀懂 Lambda 表達式的必須需要理解的地方挤安。
舉一個栗子:
// 需要調(diào)用函數(shù)式接口的方法
public static void process(Runnable r) {
r.run();
}
// 之前的調(diào)用方法
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hello World 2");
}
};
process(r1);
// Lambda 表達方法
process(() -> System.out.println("Hello World 3"));
相信如果有之前實現(xiàn)的方法作比較,我們會很好明白丧鸯,但是如果你擋住上邊的實現(xiàn)蛤铜,去看下邊的 lambda 我相信好多人跟我一樣懵逼。這是什么鬼骡送? 雖然我知道結(jié)果會是 Hello World 3 昂羡,但是這究竟是什么?
思路是這樣的:
- process 方法只接受 Runnable 的具體實現(xiàn)類
- Lambda 可以作為函數(shù)式接口的一個實現(xiàn)實例
-
() -> System.out.println("Hello World 3")
應(yīng)該就是 Runnable 的一個具體實現(xiàn)摔踱。 - Lambda 是函數(shù)式接口的抽象方法的實現(xiàn)虐先,也就是這句話同時也是 run() 方法的具體實現(xiàn),run方法不需要參數(shù)派敷,所以箭頭前邊為一個空的 ()蛹批。() -> void代表 了參數(shù)列表為空,且返回void的函數(shù)篮愉。
這充分說明 Lambda 寫得少想得多的特點腐芍。但是我們也發(fā)現(xiàn)了他的一個優(yōu)點就是「簡單易讀」,我們看到這句話第一個反應(yīng)就是:它將打印出 Hello World 3 试躏。
方法簽名
Java 方法簽名 :java 的方法簽名由 全類名.方法名(形參數(shù)據(jù)類型列表)返回值數(shù)據(jù)類型 決定,在方法存在重載的時候猪勇,方法返回值沒有什么意義,是由方法名和參數(shù)列表決定的颠蕴。
Lambda 表達式的簽名:函數(shù)式接口的抽象方法的簽名基本上就是Lambda表達式的簽名泣刹。
書中舉了個錯誤的例子:ApplePredicate<Apple> p = (Apple a) -> a.getWeight();
因為之前我們定義的時候:
public interface ApplePredicate{ boolean test (Apple apple); }
我們可以看到,函數(shù)式接口 ApplePredicate test 的方法簽名中犀被,返回值為 boolean 但是錯誤代碼中返回了int值椅您,所以方法簽名不同。
動手實現(xiàn)一個 Lambda 付諸實踐
動手練習(xí):將下列代買轉(zhuǎn)化為 Lambda 實現(xiàn):
public static String proccessFile() throws IOException {
try (BufferedReader br = new BufferedReader((new FileReader("data.txt")))) {
return br.readLine();
}
}
-
行為參數(shù)化
實際操作中我們可能需要對文件進行不同的操作寡键,比如讀取兩行掀泳,讀取最后一行。所以我們要將
proccessFile ()
方法 對文件進行不同操作的行為西轩,進行參數(shù)化员舵。我們期待的結(jié)果是 通過 processFile 拿到文件的讀取結(jié)果,具體怎么讀取應(yīng)該由接口的抽象方法具體實現(xiàn)藕畔,所以 processFile 的行為就是如何操作文件固灵,我們需要將他參數(shù)化。
-
使用函數(shù)接口來傳遞行為
為了之后我們能夠使用 Lambda 來實現(xiàn)這個功能劫流,我們需要創(chuàng)建一個 函數(shù)式接口巫玻。對于初學(xué)者來說這點應(yīng)該是最難得丛忆,我們應(yīng)該如何創(chuàng)建這個函數(shù)式接口。
- 明確任務(wù)條件: 讀取文件需要一個文件的讀取流 這里假定是字符流 BufferReader
- 明確任務(wù)結(jié)果: 返回讀取的結(jié)果仍秤,應(yīng)該是一個 String
即 BufferReader -> String 的過程
```
interface BufferedReaderProcessor {
String process(BufferedReader bufferedReader);
}
```
-
執(zhí)行一個行為
執(zhí)行這個行為將需要用到processFile
方法了熄诡,該方法參數(shù)為函數(shù)式接口BufferedReaderProcessor
,用該接口的是實例來進行文件操作诗力,具體怎么操作由該實例決定:public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } }
-
改為 Lambda 形式完成 process 的具體實現(xiàn)
通過之前的學(xué)習(xí)凰浮,我們知道了一個重要的概念就是 Lambda 可以作為,Lambda 是函數(shù)式接口的抽象方法的實現(xiàn)苇本,但它可以作為整個接口的一個具體實例袜茧。。
- 需要實現(xiàn)這個接口的具體方法
- 整體作為實例傳遞給 processFile
假設(shè)我們現(xiàn)在需要完成一讀取文章前兩行的操作:
-
(parameters) -> expression
(BufferReader br) -> br.readLine()+br.readLine();
-
(parameters) -> { statements; }
String readline = proccessFile((BufferedReader br) -> { //readLine 方法會有警告 瓣窄,之后會學(xué)習(xí)如何處理 lambda 中的警告 這里重點在于 lambda 的實現(xiàn)笛厦。 String line1 = br.readLine(); String line2 = br.readLine(); return line1 + line2; });
類型檢查
Lambda 可以作為函數(shù)式接口生成一個實例。然而俺夕,Lambda 表達式本身并不包含它在實現(xiàn)哪個函數(shù)式接口的信息
正如我們文章開頭提到的那樣裳凸,Lambda 本身并沒有什么意義,只有結(jié)合上下文劝贸,更通俗的說是等號左邊的內(nèi)容姨谷,才是我們最終想要的 函數(shù)式接口具體實現(xiàn) 對象。
Lambda的類型是從使用Lambda的上下文推斷出來的映九。
這里的上下文包括: 方法簽名(參數(shù)梦湘,返回值),以及使用這個 Lambda 的方法的參數(shù)類型件甥。
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
這個例子還是延續(xù)第一篇中的那個篩選蘋果的例子捌议,書中給出了很好的解讀這里就不多贅述直接上圖:
看到這里我只能說這本書翻譯的太好了。
依次類推我們看地方代碼的時候如果它使用了 Lambda 方式書寫代碼嚼蚀,我們可以通過以下方式來找到這個 Lambda 的含義:
- 找到使用 Lambda 函數(shù)式參數(shù)方法的方法定義。
- 查看Lambda 所需要實例化的抽象接口的內(nèi)唯一的一個抽象方法的方法簽名(這里是 T -> boolen)
- 檢查 Lambda 表達式是否滿足這個抽象方法的方法簽名即可管挟。
聰明的人可能發(fā)現(xiàn)了貓膩轿曙,我們可能好多的函數(shù)式接口的抽象方法的方法簽名都是 T -> boolen,那么 Lambda 的使用會不會出現(xiàn)問題 ?
事實上僻孝,同一個 Lambda 表達式导帝,如果 Lambda 表達式的方法簽名 = 函數(shù)式接口的抽象方法接口 那么這個這個 Lambda 就是有效的。
這就需要在我們書寫或者閱讀的時候必須結(jié)合上下文穿铆,而 JVM 需要做的事情更多您单,但具體原理并不影響我們使用,所以就先不探討荞雏。
類型推斷
之前說過 Lambda 表達式還可以繼續(xù)簡單化代碼虐秦,剛才我們學(xué)習(xí)了類型檢查平酿,相同的 Lambda 表達式可以匹配不同的函數(shù)接口,JVM 可以根據(jù)我們使用 Lambda 表達式的上下文來決定匹配什么樣的函數(shù)接口來接收此表達式悦陋。
如果這個 Lambda 的目標類型是可以推斷出來的蜈彼,而參數(shù)類型也只有一種的時候,我們可以省略參數(shù)的類型:
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));
Lambda表達式有多個參數(shù)俺驶,代碼可讀性的好處就更為明顯,以下兩個方式是等價的幸逆。
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
書中也給了我們提醒,有的時候沒有類型推斷的 Lambda 表達式更易讀暮现,有時候去掉看起來更好还绘。
需要我們自己去選擇。
至此我們已經(jīng)成功完成了一次lambda 的實踐過程栖袋。通過學(xué)習(xí)我的體會就是想要 lambda 的學(xué)習(xí)還是需要多加練習(xí)拍顷。否則總會處在放下課本就忘得狀態(tài)。至此我們學(xué)習(xí)完了栋荸,課本的3.3章的內(nèi)容菇怀。而之后將會介紹一個新的概念叫「方法引用」,它將會使代碼更加簡潔晌块,但是同時也帶來了更大的挑戰(zhàn)爱沟,一起來加油吧。