和Lambdas的第一次親密接觸

Lambda工程是即將到來(lái)的Java8的一大主題,可能也是程序員們最期待已久的東西。隨著Java lambdas的到來(lái),還有一個(gè)有趣的東西被附帶的加進(jìn)了Java語(yǔ)言——defender(守衛(wèi)者)方法。在這篇文章里,我的目的是要看看面紗后的東西 ——看看在運(yùn)行時(shí)環(huán)境里lambdas是表現(xiàn)的,在方法的調(diào)度過(guò)程中涉及到哪些字節(jié)碼指令。
盡管Java 8還沒(méi)有正式發(fā)布,我們?nèi)匀豢梢韵螺d各種平臺(tái)上的早期預(yù)覽版,在其上做簡(jiǎn)單的嘗試。
你也想試試lambdas,是嗎?
如果你熟悉其它的還有l(wèi)ambda表達(dá)式的編程語(yǔ)言,比如Groovy 或 Ruby,當(dāng)?shù)谝谎劭吹絁ava里的lambda時(shí),你也許會(huì)吃驚于它的不簡(jiǎn)單。在Java里,lambda表達(dá)式是“SAM”(Single Abstract Method)——一個(gè)含有一個(gè)抽象方法的接口(是的,現(xiàn)在接口里可以含有一個(gè)非抽象的方法,defender守衛(wèi)方法)。
舉個(gè)例子,大家熟知的Runnable接口就可以完美的被當(dāng)作一個(gè)SAM類型:
- Runnable r = () -> System.out.println("hello lambda!");
 
,這同樣也適用于Comparable接口:
- Comparator<Integer> cmp = (x, y) -> (x < y) ? -1 : ((x > y) ? 1 : 0);
 
寫成下面的樣子也是一樣的:
- Comparator<Integer> cmp = (x, y) -> {
 - return (x < y) ? -1 : ((x > y) ? 1 : 0);
 - };
 
從中可以看出,單行的lambda表達(dá)式似乎是隱含了一個(gè)return語(yǔ)句。
那么,如何寫一個(gè)能接受lambda表達(dá)式作為參數(shù)的方法呢?這樣,你需要先把這個(gè)參數(shù)聲明成函數(shù)式的接口,然后把lambda傳入:
- interface Action {
 - void run(String param);
 - }
 - public void execute(Action action){
 - action.run("Hello!");
 - }
 
一旦有了一個(gè)能將函數(shù)式接口作為參數(shù)的方法,我們就可以像下面這樣調(diào)用它:
- execute((String s) -> System.out.println(s));
 
還可以更簡(jiǎn)潔,這個(gè)表達(dá)式可以被替換成對(duì)一個(gè)方法的引用,因?yàn)樗皇菃蝹€(gè)方法,而且它們的參數(shù)是相同的:
- execute(System.out::println);
 
然而,如果參數(shù)上有任何其它形式的變化,我們就不能直接引用方法,必須寫全lambda表達(dá)式:
- execute((String s) -> System.out.println("*" + s + "*"));
 
我覺(jué)得這種語(yǔ)法還是相當(dāng)漂亮的,現(xiàn)在,Java語(yǔ)言里有了一個(gè)非常優(yōu)雅的lambdas解決方案,盡管Java里并不存在函數(shù)式類型。
JDK 8里的函數(shù)式接口
我們已經(jīng)知道,lambda在運(yùn)行時(shí)的表現(xiàn)形式是一個(gè)函數(shù)式的接口(或“SAM類型”)——只有一個(gè)抽象方法的接口。盡管JDK里已經(jīng)有了不少這樣的接口,例如Runnable 和 Comparable ,它們符合這種標(biāo)準(zhǔn),但很顯然,對(duì)于一個(gè)新API的進(jìn)化來(lái)說(shuō),這是不夠的。我們不可能所有地方都用Runnables接口。
在JDK 8 里有個(gè)新包,java.util.function,里面包含了很多函數(shù)式接口,都是提供在新API里使用的。我不想把它們?nèi)谐鰜?lái)——你們自己可以去看一下,學(xué)習(xí)一下這個(gè)新包 
但看起來(lái)這個(gè)新包在不斷的變化,經(jīng)常性的一些新接口會(huì)出現(xiàn)而另一些會(huì)消失。例如,以前曾有過(guò) java.util.function.Block 這個(gè)類,最新的版本中卻沒(méi)有它,我寫這篇博客時(shí)使用的版本是:
- anton$ java -version
 - openjdk version "1.8.0-ea"
 - OpenJDK Runtime Environment (build 1.8.0-ea-b75)
 - OpenJDK 64-Bit Server VM (build 25.0-b15, mixed mode)
 
我研究發(fā)現(xiàn),它現(xiàn)在被 Consumer 接口替代,collection包里的所有新方法都將使用它。例如,Collection接口里定義了forEach方法,如下:
- public default void forEach(Consumer<? super T> consumer) {
 - for (T t : this) {
 - consumer.accept(t);
 - }
 - }
 
Consumer接口里一個(gè)有趣地方是,它實(shí)際上定義了一個(gè)抽象方法——accept(T t)和一個(gè)defender方法——Consumer<T> chain(Consumer<? extend T> consumer)。這就是說(shuō)你可以鏈?zhǔn)秸{(diào)用這個(gè)接口。我不確定如何使用,因?yàn)槲以贘DK包里沒(méi)有找到chain(..)的使用方法說(shuō)明。
我還發(fā)現(xiàn)所有的接口都使用了@FunctionalInterface運(yùn)行時(shí)注注解注釋。這個(gè)注釋不僅僅是個(gè)說(shuō)明,它還被javac使用來(lái)驗(yàn)證這個(gè)接口是否真是一個(gè)函數(shù)式接口,是否至少有一個(gè)抽象方法在里面。
所以,如果我們來(lái)編譯下面的這段代碼
- @FunctionalInterface
 - interface Action {
 - void run(String param);
 - void stop(String param);
 - }
 
編譯器會(huì)告訴我們:
- java: Unexpected @FunctionalInterface annotation
 - Action is not a functional interface
 - multiple non-overriding abstract methods found in interface Action
 
而下面的就能編譯通過(guò):
- @FunctionalInterface
 - interface Action {
 - void run(String param);
 - default void stop(String param){}
 - }
 
反編譯lambdas
我對(duì)語(yǔ)法語(yǔ)言特征其實(shí)并不是很好奇,我更好奇的是這些特征在運(yùn)行時(shí)的表現(xiàn)形式,這就是為什么我像往常一樣,拿起我喜愛(ài)的javap工具,開(kāi)始查看lambdas里的這些類的字節(jié)碼。
目前(在Java 7之前),如果你想在Java里模擬lambdas,你需要定義一個(gè)匿名的內(nèi)部類。它在編譯后會(huì)產(chǎn)生一個(gè)具體的class。如果你在一段代碼里定義了多個(gè)這樣的類,你會(huì)發(fā)現(xiàn)這些類后面會(huì)跟著一些數(shù)字。那lambdas也會(huì)這樣嗎?
看看下面的這段代碼:
- public class Main {
 - @FunctionalInterface
 - interface Action {
 - Object run(String s);
 - }
 - public void action(Action action){
 - action.run("Hello!");
 - }
 - public static void main(String[] args) {
 - new Main().action((String s) -> System.out.print("*" + s + "*"));
 - }
 - }
 
編譯產(chǎn)生了兩個(gè)類文件:Main.class 和 Main$Action.class,沒(méi)有匿名類實(shí)現(xiàn)里那樣的序號(hào)化的類。那么,在Main.class里應(yīng)該會(huì)有一些東西來(lái)代表我在main方法里定義的lambdas表達(dá)式的實(shí)現(xiàn)。
- $ javap -p Main
 - Warning: Binary file Main contains com.zt.Main
 - Compiled from "Main.java"
 - public class com.zt.Main {
 - public com.zt.Main();
 - public void action(com.zt.Main$Action);
 - public static void main(java.lang.String[]);
 - private static java.lang.Object lambda$0(java.lang.String);
 - }
 
啊哈!編譯類了產(chǎn)生了lambda$0方法!使用-c -v指示符會(huì)讓我們看到真正的字節(jié)碼,以及常量池的定義。
main方法里顯示,invokedynamic被用來(lái)調(diào)度這個(gè)調(diào)用:
- public static void main(java.lang.String[]);
 - Code:
 - 0: new #4 // class com/zt/Main
 - 3: dup
 - 4: invokespecial #5 // Method "":()V
 - 7: invokedynamic #6, 0 // InvokeDynamic #0:lambda:()Lcom/zt/Main$Action;
 - 12: invokevirtual #7 // Method action:(Lcom/zt/Main$Action;)V
 - 15: return
 
而在常量池里,你也可以找到運(yùn)行時(shí)的啟動(dòng)方法:
- BootstrapMethods:
 - 0: #40 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
 - Method arguments:
 - #41 invokeinterface com/zt/Main$Action.run:(Ljava/lang/String;)Ljava/lang/Object;
 - #42 invokestatic com/zt/Main.lambda$0:(Ljava/lang/String;)Ljava/lang/Object;
 - #43 (Ljava/lang/String;)Ljava/lang/Object;
 
你會(huì)發(fā)現(xiàn)到處都是在使用MethodHandle API,但我們現(xiàn)在不打算深入到里面。現(xiàn)在我們可以確認(rèn)一點(diǎn),我們的定義是引用了編譯出來(lái)lambda$0方法。
我很好奇,如果我定義一個(gè)相同名字的靜態(tài)方法會(huì)怎樣——畢竟“lambda$0”是一個(gè)有效的標(biāo)識(shí)符!于是,我定義了自己的lambda$0方法:
- public static Object lambda$0(String s){ return null; }
 
而編譯失敗,編譯器不允許我在代碼了擁有這個(gè)方法:
- java: the symbol lambda$0(java.lang.String) conflicts with a
 - compiler-synthesized symbol in com.zt.Main
 
同時(shí),如果我刪掉這段定義lambdas表達(dá)式的代碼,程序能順利編譯通過(guò)。這就是說(shuō),lambdas表達(dá)式在編譯期間會(huì)比類里的其它數(shù)據(jù)早先分析,不過(guò)這只是我的猜測(cè)。
請(qǐng)注意:在這個(gè)例子中,lambda并沒(méi)有去引用任何變量,也沒(méi)有引用類內(nèi)部的任何方法。這就是為什么產(chǎn)生的lambda$0方法是靜態(tài)的。如果lambdas引用了上下文中的變量或方法,那生成的將是一個(gè)非靜態(tài)方法。所以,不要被這個(gè)例子誤導(dǎo)——lambdas是可以捕獲上下文環(huán)境內(nèi)容的!
總結(jié)lambdas
我可以毫無(wú)疑問(wèn)的說(shuō),lambdas和伴隨它一起的各種特征(守衛(wèi)方法(defender)
,升級(jí)的集合類庫(kù))將很快給Java帶來(lái)巨大的沖擊。它的語(yǔ)法相當(dāng)?shù)暮?jiǎn)單,一旦程序員們意識(shí)到這些功能給開(kāi)發(fā)效率帶來(lái)的好處,我們將會(huì)看到大量的程序員都會(huì)運(yùn)用這個(gè)功能。
看看lambdas會(huì)編譯成什么樣子,這對(duì)于我來(lái)說(shuō)是一件非常有趣的事情,我很開(kāi)心,因?yàn)槲铱吹竭@些所有的invokedynamic指令調(diào)用都沒(méi)有出現(xiàn)匿名內(nèi)部類。















 
 
 





 
 
 
 