abstrak:前言本文受啟發(fā)于Trisha Gee在JavaOne 2016的主題演講Refactoring to Java 8。Java 8已經(jīng)發(fā)行兩年多,但很多人仍然在使用JDK7。對企業(yè)來說,技術(shù)上謹(jǐn)慎未必是壞事,但對個人學(xué)習(xí)而言,不去學(xué)習(xí)新技術(shù)就很可能被技術(shù)拋棄。Java 8一個重要的變更是引入Lambda表達式(lambda expression),這聽起來似乎很牛,有種我雖然不知道Lambda表達式
前言
本文受啟發(fā)于Trisha Gee在JavaOne 2016的主題演講Refactoring to Java 8。
Java 8已經(jīng)發(fā)行兩年多,但很多人仍然在使用JDK7。對企業(yè)來說,技術(shù)上謹(jǐn)慎未必是壞事,但對個人學(xué)習(xí)而言,不去學(xué)習(xí)新技術(shù)就很可能被技術(shù)拋棄。Java 8一個重要的變更是引入Lambda表達式(lambda expression),這聽起來似乎很牛,有種我雖然不知道Lambda表達式是什么,但我仍然覺得很厲害的感覺。不要怕,具體到語言層面上Lambda表達式不過是一種新的語法而已,有了它,Java將開啟函數(shù)式編程的大門。
為什么需要Lambda表達式
不要糾結(jié)什么是Lambda表達式、什么是函數(shù)式編程。先來看一下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表達式一個常見的用法是取代(某些)匿名內(nèi)部類,但Lambda表達式的作用不限于此。
Lambda表達式的原理
剛接觸Lambda表達式可能覺得它很神奇:不需要聲明類或者方法的名字,就可以直接定義函數(shù)。這看似是編譯器為匿名內(nèi)部類簡寫提供的一個小把戲,但事實上并非如此,Lambda表達式實際上是通過invokedynamic指令來實現(xiàn)的。先別管這么多,下面是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
通過上例可以發(fā)現(xiàn):
Lambda表達式是有類型的,賦值操作的左邊就是類型。Lambda表達式的類型實際上是對應(yīng)接口的類型。
Lambda表達式可以包含多行代碼,需要用大括號把代碼塊括起來,就像寫函數(shù)體那樣。
大多數(shù)時候,Lambda表達式的參數(shù)表可以省略類型,就像代碼2和5那樣。這得益于javac的類型推導(dǎo)機制,編譯器可以根據(jù)上下文推導(dǎo)出類型信息。
表面上看起來每個Lambda表達式都是原來匿名內(nèi)部類的簡寫形式,該內(nèi)部類實現(xiàn)了某個函數(shù)接口(Functional Interface),但事實比這稍微復(fù)雜一些,這里不再展開。所謂函數(shù)接口是指內(nèi)部只有一個接口函數(shù)的接口。Java是強類型語言,無論有沒有顯式指明,每個變量和對象都必須有明確的類型,沒有顯式指定的時候編譯器會嘗試確定類型。Lambda表達式的類型就是對應(yīng)函數(shù)接口的類型。
Lambda表達式和Stream
Lambda表達式的另一個重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一組元素的序列,支持對這些元素進行各種操作,而這些操作是通過Lambda表達式指定的。可以把Stream看作Java Collection的一種視圖,就像迭代器是容器的一種視圖那樣(但Stream不會修改容器中的內(nèi)容)。下面例子展示了Stream的常見用法。
例子1
假設(shè)需要從一個字符串列表中選出以數(shù)字開頭的字符串并輸出,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.選出以數(shù)字開頭的字符串 .forEach(str -> System.out.println(str));// 3.輸出字符串
上述代碼首先1. 調(diào)用List.stream()方法得到容器的Stream,2. 然后調(diào)用filter()方法過濾出以數(shù)字開頭的字符串,3. 最后調(diào)用forEach()方法輸出結(jié)果。
使用Stream有兩個明顯的好處:
減少了模板代碼,只用Lambda表達式指明所需操作,代碼語義更加明確、便于閱讀。
將外部迭代改成了Stream的內(nèi)部迭代,方便了JVM本身對迭代過程做優(yōu)化(比如可以并行迭代)。
例子2
假設(shè)需要從一個字符串列表中,選出所有不以數(shù)字開頭的字符串,將其轉(zhuǎn)換成大寫形式,并把結(jié)果放到新的集合當(dāng)中。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.選出不以數(shù)字開頭的字符串 .map(String::toUpperCase)// 3.轉(zhuǎn)換成大寫形式 .collect(Collectors.toSet());// 4.生成結(jié)果集
上述代碼首先1. 調(diào)用List.stream()方法得到容器的Stream,2. 然后調(diào)用filter()方法選出不以數(shù)字開頭的字符串,3. 之后調(diào)用map()方法將字符串轉(zhuǎn)換成大寫形式,4. 最后調(diào)用collect()方法將結(jié)果轉(zhuǎn)換成Set。這個例子還向我們展示了方法引用(method references,代碼中標(biāo)號3處)以及收集器(Collector,代碼中標(biāo)號4處)的用法,這里不再展開說明。
通過這個例子我們看到了Stream鏈?zhǔn)讲僮鳎炊鄠€操作可以連成一串。不用擔(dān)心這會導(dǎo)致對容器的多次迭代,因為不是每個Stream的操作都會立即執(zhí)行。Stream的操作分成兩類,一類是中間操作(intermediate operations),另一類是結(jié)束操作(terminal operation),只有結(jié)束操作才會導(dǎo)致真正的代碼執(zhí)行,中間操作只會做一些標(biāo)記,表示需要對Stream進行某種操作。這意味著可以在Stream上通過關(guān)聯(lián)多種操作,但最終只需要一次迭代。如果你熟悉Spark RDD,對此應(yīng)該并不陌生。
結(jié)語
Java 8引入Lambda表達式,從此打開了函數(shù)式編程的大門。如果你之前不了解函數(shù)式編程,不必糾結(jié)于這個概念。編程過程中簡潔明了的書寫形式以及強大的Stream API會讓你很快熟悉Lambda表達式的。
本文只對Java Lambda表達式的基本介紹,希望能夠激發(fā)讀者對Java函數(shù)式編程的興趣。如果本文能夠讓你覺得Lambda表達式很好玩,函數(shù)式編程很有趣,并產(chǎn)生了進一步學(xué)習(xí)的欲望,那就再好不過了。文末參考文獻中列出了一些有用的資源。