Lambda表示式之爭:Scala vs Java 8
最近幾年Lambda表示式風靡於程式設計界。很多現代程式語言都把它作為函數語言程式設計的基本組成部分。基於JVM的程式語言如Scala、Groovy及Clojure把它作為關鍵部分整合在語言中。而如今,(最終)Java 8也加入了這個有趣的行列。
Lambda表示式最有意思的地方在於,在JVM的角度來看它是完全不可見的。在JVM中沒有匿名函式或Lambda表示式的概念。JVM唯一知道是位元組碼。位元組碼是一個嚴格的OO規範。由語言的創造者和編譯者通過這些限制來建立新的、高階的語言元素。
我們第一次遇到Lambda表示式是需要在Takipi中增加對Scala的支援,所以不得不深入瞭解Scala的編譯器。而這時Java 8也正處在關鍵時刻。我猜想Scala和Java編譯器對Lambda表示式的實現肯定會非常有趣。結果讓我極為驚訝。
為了演示這些內容,我寫了一個簡單的Lambda表示式,功能是將一個字串列表轉換為它們長度的列表。
Java:
List names = Arrays.asList("1", "2", "3"); Stream lengths = names.stream().map(name -> name.length());
Scala:
val names = List("1", "2", "3") val lengths = names.map(name => name.length)
不要被它表面的簡單所迷惑,後面執行了相當複雜的過程。
我們從Scala開始
程式碼
我使用 javap 來檢視通過Scala編譯器生成的.class檔案的位元組碼的內容。讓我們看一下位元組碼的結果(這才是JVM真正執行的內容)。
//將變數名載入到棧中(JVM視為變數#2),先儲存在這,之後會在map函式中用到 aload_2
接下來的事情就變得更有趣了,一個由編譯器生成的synthetic的例項建立並初始化(譯者注:Synthetic類是指由JVM執行時生成的類)。非常有意思的是,Lambda作為整個方法的一部分來定義的,但它實際上完全存在於我們類的外部。
new myLambdas/Lambda1$$anonfun$1 //例項化Lambda物件 dup //把它加入棧中 //最後,呼叫建構函式.記住,這是源自JVM的一個簡單物件 invokespecial myLambdas/Lambda1$$anonfun$1/()V //這個兩行載入immutable.List CanBuildFrom工廠,該工廠能生成新的list。工廠模式是Scala的集合架構的一部分。 getstatic scala/collection/immutable/List$/MODULE$ Lscala/collection/immutable/List$; invokevirtual scala/collection/immutable/List$/canBuildFrom() Lscala/collection/generic/CanBuildFrom; //現在,棧上已經有了Lambda物件及工廠,下一階段就可以呼叫map函式。 //你應該還記得,我們在一開始的時候將名稱變數載入到了棧中。我們現在可以用它來實現map方法的呼叫了。 //map方法接受一個Lambda物件和一個工廠,生成一個長度的list。 invokevirtual scala/collection/immutable/List/map(Lscala/Function1; Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;
但是請稍等,Lambda物件內部做了什麼事情?
Lambda物件
Lambda類來繼承自scala.runtime.AbstractFunction1。通過這種方式,map() 函式可以多型呼叫重寫後的 apply() 方法,apply()程式碼如下:
//這段程式碼是載入this及目標物件,檢測它是不是一個字串,然後呼叫另一個過載後的、真正工作的apply方法,最後包裝返回結果 aload_0//載入this aload_1//載入字串引數 checkcast java/lang/String//確保是一個字串 - 得到一個Object // 呼叫synthetic類的apply()方法 invokevirtual myLambdas/Lambda1$$anonfun$1/apply(Ljava/lang/String;)I //包裝結果 invokestatic scala/runtime/BoxesRunTime/boxToInteger(I)Ljava/lang/Integer areturn
真正的執行.length() 操作的程式碼巢狀在另個一apply方法中,該方法正如我們期望的一樣,簡單的返回了字串的長度。
唷……,走了好長的一段路才到這。
aload_1 invokevirtual java/lang/String/length()I ireturn
我們在上面只是寫了一行簡單的程式碼,但是卻產生了許多的位元組碼,包括一個額外的類和一堆方法。但是,這絕不是在勸阻我們不要用Lambda(我們是在Scala中寫程式碼,而不是C)。這僅僅是為了展示這種結構後面的複雜性。
我相當期待Java 8也是用這種方式實現的,但是令人驚訝的時,java採取了完全不同的方式。
Java 8:一種新的方式
Java 8產生的位元組碼比較短,但是還有更令人驚訝的東西。它剛開始簡單的載入了名稱變數,然後呼叫 stream() 方法,但是接下做了一些非常好的優化。它沒有建立一個新的物件來包裝Lambda函式,而是使用了新的 invokeDynamic 指令,該指令是Java 7時增加的,這個地方的用於呼叫真實的Lambda函式。
aload_1 // 載入名稱變數 //呼叫stream()方法 invokeinterface java/util/List.stream:()Ljava/util/stream/Stream; //invokeDynamic指令魔法! invokedynamic #0:apply:()Ljava/util/function/Function; //呼叫map()方法 invokeinterface java/util/stream/Stream.map: (Ljava/util/function/Function;)Ljava/util/stream/Stream;
InvokeDynamic魔法:這條JVM指令在Java 7中增加,用於減少JVM的限制,允許動態語言在執行時繫結符號。而在這之前,所有的連結都是靜態的,在程式碼編譯的時候就由JVM完成。
動態連結:如果你看過invokedynamic指令,你會發現沒有引用指向真正的Lambda函式(即lambda$0)。答案歸結於invokedynamic指令的設計,但是更簡短的答案是Lambda表示式的簽名,就我們的例子來說是
//一個名為lamda$0的函式,獲取一個字串,返回一個整數 lambdas/Lambda1.lambda$0:(Ljava/lang/String;)Ljava/lang/Integer;
儲存在.class的一個單獨的表中,該表作為#0引數傳遞給指令。這個新的表確實改變了位元組碼規範的結構,這是多年之後的第一次改變,這同樣需要採取Takipi的錯誤分析引擎。
Lambda程式碼
這段程式碼是真正的Lambda表示式。非常容易,簡單地載入字串引數,呼叫length()方法幷包裝成結果。請注意,它是編譯成了一個靜態函式,避免像之前看到的Scala一樣,傳入額外的this物件。
aload_0 invokevirtual java/lang/String.length:() invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer; areturn
這是invokedynamic方式的另一個優點,它允許我們通過多型的方式來呼叫 map() 函式,且不需要包裝物件或呼叫虛擬的的重寫方法。非常酷!
總結
Java看起非常具有吸引力,最“嚴格”的現代語言現在開始使用動態連結來增加Lambda表示式的功能。該方式也是非常有效的一種方式,不需要載入和編譯額外的類,Lambda方法只是我們類中一個簡單的私有方法。
Java 8確實對Java 7引入的新的技術做了很多優化,使用了非常直接的方式實現了對Lambda表示式的支援。非常高興能看到像Java這樣“端莊”的女士能教我們一些戲法。
相關文章
- Java 8 Lambda 表示式Java
- java 8 lambda表示式Java
- java8 新特性之Lambda 表示式Java
- Java8-Lambda表示式Java
- Java之lambda表示式Java
- Java8的Lambda表示式Java
- java8特性-lambda表示式Java
- 《Java 8 in Action》Chapter 3:Lambda表示式JavaAPT
- Java 8新特性(一):Lambda表示式Java
- Java 8 lambda 表示式10個示例Java
- Java8中的Lambda表示式Java
- java8學習:lambda表示式(2)Java
- java8學習:lambda表示式(1)Java
- Java8新特性(一)-Lambda表示式Java
- Java8新特性(1):Lambda表示式Java
- Java8-增強版Comparator和排序之Lambda表示式Java排序
- java8的新特性之lambda表示式和方法引用Java
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- Java Lambda表示式Java
- Java 8 Lambda表示式一看就會Java
- Java8 Lambda表示式、Optional類淺析Java
- Java的Lambda表示式Java
- 好程式設計師分享java8新特性之Lambda表示式程式設計師Java
- Java 8:一文帶你掌握 Lambda 表示式Java
- Java 8: Lambda表示式增強版Comparator和排序Java排序
- Java筆記:Lambda表示式Java筆記
- Java 中的 Lambda 表示式Java
- Java 基礎 —— Lambda 表示式Java
- Java lambda表示式基本使用Java
- c++之lambda表示式C++
- Java8特性詳解 lambda表示式(一):使用篇Java
- Java8新特性——從Lambda表示式到Stream流Java
- Java8特性詳解 lambda表示式(二):流式處理中的lambdaJava
- ?Java8新特性之Lambda表示式,函式式介面,方法引用和default關鍵字Java函式
- Java中lambda表示式詳解Java
- Java中Lambda表示式的使用Java
- Java8 新語法習慣 (級聯 lambda 表示式)Java
- Java8特性詳解 lambda表示式(三):原理篇Java