不要糾結什么是Lambda表達式,什么是函數式編程,先來看一下java8新的語法特性帶來的便利之處,相信你會過目不忘的。
在有Lambda表達式之前,要新建一個線程,需要這樣寫:
package com.lambdatest;/** * Created by root on 3/6/17. */public class Main { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.PRintln("Thread run()"); } }).start(); }}有Lambda表達式之后,則可以這樣寫:
package com.lambdatest;/** * Created by root on 3/6/17. */public class Main { public static void main(String[] args) { new Thread( () -> System.out.println("Thread run()") ).start(); }}正如你所見,之前無用的模板代碼不見了!如上所示,Lambda表達式一個常見的用法是取代(某些)匿名內部類,但Lambda表達式的作用不限于此。
Lambda表達式的原理
剛接觸Lambda表達式可能覺得它很神奇:不需要聲明類或者方法的名字,就可以直接定義函數。這看似是編譯器為匿名內部類簡寫提供的一個小把戲,但事實上并非如此,Lambda表達式實際上是通過invokedynamic指令來實現的。先別管這么多,下面是Lambda表達式幾種可能的書寫形式,“看起來”并不是很難理解。
Runnable run = () -> System.out.println("Hello World"); ActionListener listener = event -> System.out.println("button clicked"); Runnable multiline = () -> { System.out.println("Hello"); System.out.println("World"); }; BinaryOperator<Long> add = (Long x, Long y) -> x + y; BinaryOperator<Long> addImplicit = (x, y) -> x + y;通過上面的代碼可以發現:
Lambda表達式是有類型的,賦值操作的左邊就是類型。Lambda表達式的類型實際上是對應接口的類型。Lambda表達式可以包含多行代碼,需要用大括號把代碼塊括起來,就像寫函數一樣。大多數時候,Lambda表達式的參數表可以省略類型,這得益于javac的類型推導機制,編譯器可以根據上下文推導出類型信息。表面上看起來每個Lambda表達式都是原來匿名內部類的簡寫形式,該內部類實現了某個函數接口(Functional Interface),但事實上比這稍微復雜一些,這里不再展開。所謂函數接口是指內部只有一個接口函數的接口。Java是強類型語言,無論有沒有顯式指明,每個變量和對象都必須有明確的類型,沒有顯式指定的時候編譯器會嘗試確定類型。Lambda表達式的類型就是對應函數接口的類型。Lambda表達式和Stream
Lambda表達式的另一個重要的用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一組元素的序列,支持對這些元素進行各種操作,而這些操作是通過Lambda表達式指定的??梢园裇tream看作Java Collection的一種視圖,就像迭代器是容器的一種視圖那樣(但Stream不會修改容器中的內容)。下面例子展示了Stream的常見用法:例子1
假設需要從一個字符串列表中選出以數字開頭的字符串并輸出,Java 7之前是這樣寫的:package com.lambdatest;import java.util.Arrays;import java.util.List;/** * Created by root on 3/6/17. */public class Main { public static void main(String[] args) { 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需要這樣寫:package com.lambdatest;import java.util.Arrays;import java.util.List;/** * Created by root on 3/6/17. */public class Main { public static void main(String[] args) { List<String> list = Arrays.asList("1one", "two", "three", "4four"); list.stream() // 得到容器的Stream .filter(str -> Character.isDigit(str.charAt(0))) // 選出以數字開頭的字符串 .forEach(str -> System.out.println(str)); // 輸出字符串 }}上述代碼首先調用list.stream()方法得到容器的Stream,然后調用filter()方法過濾出以數字開頭的字符串,最后調用forEach()方法輸出結果。使用Stream有兩個明顯的好處:減少了模板代碼,只用Lambda表達式指名所需操作,代碼語義更加明確,便于閱讀。將外部迭代改成了Stream的內部迭代,方便了JVM本身對迭代過程進行優化(比如可以并行迭代)。例子2
假設要從一個字符串列表中,選出所有不以數字開頭的字符串,并將其轉換成大寫形式,并把結果放到新的集合當中。Java 8的書寫代碼如下:package com.lambdatest;import java.util.Arrays;import java.util.List;import java.util.Set;import java.util.stream.Collectors;/** * Created by root on 3/6/17. */public class Main { public static void main(String[] args) { List<String> list = Arrays.asList("1one", "two", "three", "4four"); Set<String> strSet = list.stream() // 得到容器的Stream .filter(str -> !Character.isDigit(str.charAt(0))) // 選出不以數字開頭的字符串 .map(String::toUpperCase) // 轉換成大寫形式 .collect(Collectors.toSet()); // 生成結果集 }}上面的代碼首先調用list.stream()方法得到容器的Stream,然后調用filter()方法選出不以數字開頭的字符串,之后調用map()方法將字符串轉換成大寫形式,最后調用collect()方法將結果轉換成Set。這個例子還向我們展示了方法引用(method references),以及收集器(collector)的用法,這里不再展開說明。通過這個例子我們看到了Stream鏈式操作,即多個操作可以連成一串。不用擔心這會導致對容器的多次迭代,因為不是每個Stream操作都會立即執行的。Stream的操作分為兩類,一類是中間操作(intermediate operations),另一類是結束操作(terminal operations),只有結束操作才會導致真正的代碼執行,中間操作只會做一些標記,表示需要對Stream進行某種操作,這意味著可以在Stream上通過關聯多種操作,但最終只需要一次迭代。如果你熟悉Spark RDD,對這些概念應該不陌生。
新聞熱點
疑難解答