Java Lambda表達式初探
前言
Java 8已經發行兩年多,但很多人仍然在使用JDK7。對企業來說,技術上謹慎未必是壞事,但對個人學習而言,不去學習新技術就很可能被技術拋棄。Java 8一個重要的變更是引入 Lambda表達式 ( lambda expression ),這聽起來似乎很牛,有種我雖然不知道Lambda表達式是什么,但我仍然覺得很厲害的感覺。不要怕,具體到語言層面上Lambda表達式不過是一種新的語法而已,有了它,Java將開啟函數式編程的大門。
為什么需要Lambda表達式
不要糾結什么是 Lambda 表達式、什么是函數式編程。先來看一下Java 8新的語法特性帶來的便利之處,相信你會過目不忘的。
在有 Lambda 表達式之前,要新建一個線程,需要這樣寫:
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread run()");
}
}).start();
有 Lambda 表達式之后,則可以這樣寫:
new Thread(
() -> System.out.println("Thread run()")
).start();
正如你所見,之前無用的模板代碼不見了!如上所示, Lambda表達式一個常見的用法是取代(某些)匿名內部類 ,但Lambda表達式的作用不限于此。
Lambda表達式的原理
剛接觸 Lambda 表達式可能覺得它很神奇: 不需要聲明類或者方法的名字,就可以直接定義函數 。但其實這只是編譯器給我們提供的一個小把戲,背后的原理并不難理解。下面是 Lambda 表達式幾種可能的書寫形式:
Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3
System.out.println("Hello ");
System.out.println("World");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5
通過上例可以發現:
-
Lambda表達式是有類型的,賦值操作的左邊就是類型。Lambda表達式的類型實際上是 對應接口的類型 。
-
Lambda表達式可以包含多行代碼,需要用大括號把代碼塊括起來,就像寫函數體那樣。
-
大多數時候,Lambda表達式的參數表可以省略類型,就像代碼2和5那樣。這得益于javac的 類型推導 機制,編譯器可以根據上下文推導出類型信息。
實際上每個 Lambda表達式都是原來匿名內部類的簡寫形式 ,該內部類實現了某個 函數接口 ( Functional Interface )。 所謂函數接口是指*添加了@FunctionalInterface標注 ,并且內部只有一個接口函數的接口*。Java是強類型語言,無論有沒有顯式指明,每個變量和對象都必須有明確的類型,沒有顯式指定的時候編譯器會嘗試確定類型。 Lambda表達式的類型就是對應函數接口的類型 。
Lambda表達式和Stream
Lambda表達式的另一個重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一組元素的序列,支持對這些元素進行各種操作,而這些操作是通過 Lambda 表達式指定的。可以把Stream看作Java Collection的一種視圖,就像迭代器是容器的一種視圖那樣(但 Stream 不會修改容器中的內容)。下面例子展示了 Stream 的常見用法。
例子1
假設需要從一個字符串列表中選出以數字開頭的字符串并輸出,Java 7之前需要這樣寫:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
for(String str : list){
if(Character.isDigit(str.charAt(0))){
System.out.println(str);
}
}
而Java 8就可以這樣寫:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
list.stream()// 1.得到容器的Steam
.filter(str -> Character.isDigit(str.charAt(0)))// 2.選出以數字開頭的字符串
.forEach(str -> System.out.println(str));// 3.輸出字符串
上述代碼首先1. 調用 List.stream() 方法得到容器的 Stream ,2. 然后調用 filter() 方法過濾出以數字開頭的字符串,3. 最后調用 forEach() 方法輸出結果。
使用Stream有兩個明顯的好處:
- 減少了模板代碼,只用Lambda表達式指明所需操作,代碼語義更加明確、便于閱讀。
- 將外部迭代改成了Stream的內部迭代,方便了JVM本身對迭代過程做優化(比如可以并行迭代)。
例子2
假設需要從一個字符串列表中,選出所有不以數字開頭的字符串,將其轉換成大寫形式,并把結果放到新的集合當中。Java 8書寫的代碼如下:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
Set<String> newList =
list.stream()// 1.得到容器的Stream
.filter(str -> !Character.isDigit(str.charAt(0)))// 2.選出不以數字開頭的字符串
.map(String::toUpperCase)// 3.轉換成大寫形式
.collect(Collectors.toSet());// 4.生成結果集
上述代碼首先1. 調用 List.stream() 方法得到容器的 Stream ,2. 然后調用 filter() 方法選出不以數字開頭的字符串,3. 之后調用 map() 方法將字符串轉換成大寫形式,4. 最后調用 collect() 方法將結果轉換成 Set 。這個例子還向我們展示了 方法引用 ( method references ,代碼中標號3處)以及 收集器 ( Collector ,代碼中標號4處)的用法,這里不再展開說明。
通過這個例子我們看到了 Stream 鏈式操作,即多個操作可以連成一串。不用擔心這會導致對容器的多次迭代,因為不是每個 Stream 的操作都會立即執行。 Stream 的操作分成兩類,一類是 中間操作 ( intermediate operations ),另一類是 結束操作 ( terminal operation ),只有結束操作才會導致真正的代碼執行,中間操作只會做一些標記,表示需要對 Stream 進行某種操作。這意味著可以在 Stream 上通過關聯多種操作,但最終只需要一次迭代。如果你熟悉Spark RDD,對此應該并不陌生。
結語
Java 8引入 Lambda 表達式,從此打開了函數式編程的大門。如果你之前不了解函數式編程,不必糾結于這個概念。編程過程中簡潔明了的書寫形式以及強大的 Stream API 會讓你很快熟悉 Lambda 表達式的。
本文只對 Java Lambda 表達式的基本介紹,希望能夠激發讀者對Java函數式編程的興趣。如果本文能夠讓你覺得 Lambda 表達式很好玩,函數式編程很有趣,并產生了進一步學習的欲望,那就再好不過了。文末參考文獻中列出了一些有用的資源。
參考文獻
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk
https://www.oracle.com/javaone/speakers.html#gee
來自:http://www.cnblogs.com/CarpenterLee/p/5936664.html