天天看點

Java Lambda表達式初探前言為什麼需要Lambda表達式Lambda表達式的原理Lambda表達式和Stream結語緻謝參考文獻

java 8已經發行兩年多,但很多人仍然在使用jdk7。對企業來說,技術上謹慎未必是壞事,但對個人學習而言,不去學習新技術就很可能被技術抛棄。java 8一個重要的變更是引入lambda表達式(<code>lambda expression</code>),這聽起來似乎很牛,有種我雖然不知道lambda表達式是什麼,但我仍然覺得很厲害的感覺。不要怕,具體到語言層面上lambda表達式不過是一種新的文法而已,有了它,java将開啟函數式程式設計的大門。

不要糾結什麼是lambda表達式、什麼是函數式程式設計。先來看一下java 8新的文法特性帶來的便利之處,相信你會過目不忘的。

在有lambda表達式之前,要建立一個線程,需要這樣寫:

有lambda表達式之後,則可以這樣寫:

正如你所見,之前無用的模闆代碼不見了!如上所示,lambda表達式一個常見的用法是取代(某些)匿名内部類,但lambda表達式的作用不限于此。

剛接觸lambda表達式可能覺得它很神奇:不需要聲明類或者方法的名字,就可以直接定義函數。這看似是編譯器為匿名内部類簡寫提供的一個小把戲,但事實上并非如此,lambda表達式實際上是通過invokedynamic指令來實作的。先别管這麼多,下面是lambda表達式幾種可能的書寫形式,“看起來”并不是很難了解。

通過上例可以發現:

lambda表達式是有類型的,指派操作的左邊就是類型。lambda表達式的類型實際上是對應接口的類型。 lambda表達式可以包含多行代碼,需要用大括号把代碼塊括起來,就像寫函數體那樣。 大多數時候,lambda表達式的參數表可以省略類型,就像代碼2和5那樣。這得益于javac的類型推導機制,編譯器可以根據上下文推導出類型資訊。

表面上看起來每個lambda表達式都是原來匿名内部類的簡寫形式,該内部類實作了某個函數接口(<code>functional interface</code>),但事實比這稍微複雜一些,這裡不再展開。所謂函數接口是指添加了@functionalinterface标注,并且内部隻有一個接口函數的接口。java是強類型語言,無論有沒有顯式指明,每個變量和對象都必須有明确的類型,沒有顯式指定的時候編譯器會嘗試确定類型。lambda表達式的類型就是對應函數接口的類型。

lambda表達式的另一個重要用法,是和stream一起使用。stream is a sequence of elements supporting sequential and parallel aggregate operations。stream就是一組元素的序列,支援對這些元素進行各種操作,而這些操作是通過lambda表達式指定的。可以把stream看作java collection的一種視圖,就像疊代器是容器的一種視圖那樣(但stream不會修改容器中的内容)。下面例子展示了stream的常見用法。

假設需要從一個字元串清單中選出以數字開頭的字元串并輸出,java 7之前需要這樣寫:

而java 8就可以這樣寫:

上述代碼首先1. 調用<code>list.stream()</code>方法得到容器的stream,2. 然後調用<code>filter()</code>方法過濾出以數字開頭的字元串,3. 最後調用<code>foreach()</code>方法輸出結果。

使用stream有兩個明顯的好處:

減少了模闆代碼,隻用lambda表達式指明所需操作,代碼語義更加明确、便于閱讀。 将外部疊代改成了stream的内部疊代,友善了jvm本身對疊代過程做優化(比如可以并行疊代)。

假設需要從一個字元串清單中,選出所有不以數字開頭的字元串,将其轉換成大寫形式,并把結果放到新的集合當中。java 8書寫的代碼如下:

上述代碼首先1. 調用<code>list.stream()</code>方法得到容器的stream,2. 然後調用<code>filter()</code>方法選出不以數字開頭的字元串,3. 之後調用<code>map()</code>方法将字元串轉換成大寫形式,4. 最後調用<code>collect()</code>方法将結果轉換成<code>set</code>。這個例子還向我們展示了方法引用(<code>method references</code>,代碼中标号3處)以及收集器(<code>collector</code>,代碼中标号4處)的用法,這裡不再展開說明。

通過這個例子我們看到了stream鍊式操作,即多個操作可以連成一串。不用擔心這會導緻對容器的多次疊代,因為不是每個stream的操作都會立即執行。stream的操作分成兩類,一類是中間操作(<code>intermediate operations</code>),另一類是結束操作(<code>terminal operation</code>),隻有結束操作才會導緻真正的代碼執行,中間操作隻會做一些标記,表示需要對stream進行某種操作。這意味着可以在stream上通過關聯多種操作,但最終隻需要一次疊代。如果你熟悉spark rdd,對此應該并不陌生。

java 8引入lambda表達式,從此打開了函數式程式設計的大門。如果你之前不了解函數式程式設計,不必糾結于這個概念。程式設計過程中簡潔明了的書寫形式以及強大的stream api會讓你很快熟悉lambda表達式的。

本文隻對java lambda表達式的基本介紹,希望能夠激發讀者對java函數式程式設計的興趣。如果本文能夠讓你覺得lambda表達式很好玩,函數式程式設計很有趣,并産生了進一步學習的欲望,那就再好不過了。文末參考文獻中列出了一些有用的資源。

非常感謝阿裡巴巴“西行遊學計劃”給予我們的支援,感謝阿裡中間件團隊資助我們遠美國赴舊金山參加java語言的頂級技術會議——2016 javeone大會。借此我們有機會跟國際頂尖大牛面對面的交流,并第一時間了解到java語言的最新動态。同樣感謝伴我一起遊學的兩位隊友,有ta們在異國他鄉的陪伴和關照,我的西行之旅才更加豐富多彩。這次遊學給我們帶來的驚喜,将深深留存在我們的記憶當中。

<a href="http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/lambda-quickstart/index.html">http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/lambda-quickstart/index.html</a>

<a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/stream.html">https://docs.oracle.com/javase/8/docs/api/java/util/stream/stream.html</a>

<a href="https://docs.oracle.com/javase/tutorial/java/javaoo/lambdaexpressions.html">https://docs.oracle.com/javase/tutorial/java/javaoo/lambdaexpressions.html</a>

<a href="http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html">http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html</a>

<a href="http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html">http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html</a>

<a href="http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk">http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk</a>

<a href="https://www.oracle.com/javaone/speakers.html#gee">https://www.oracle.com/javaone/speakers.html#gee</a>