您好,登錄后才能下訂單哦!
本篇內容主要講解“Scala和Java8怎么寫Lambda表達式”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Scala和Java8怎么寫Lambda表達式”吧!
Java的寫法 –
1List names = Arrays.asList("1", "2", "3");
2Stream lengths = names.stream().map(name -> name.length());
Scala的寫法 –
1.val names = List("1", "2", "3")
2.val lengths = names.map(name =>name.length)
表面上看起來非常簡單,那么后面的復雜東西是怎么搞的呢?
一起分析Scala的實現方式
The Code
我使用javap(jdk自帶的工具)去查看Scala編譯器編譯出來的class類中所包含的字節碼內容。讓我們一起看看最終的字節碼(這是JVM將真正執行的)
1.// 加載names對象引用,壓入操作棧(JVM把它當成變量#2)
2.// 它將停留一會,直到被map函數調用.
3.aload_2
接下來的東西變得更加有趣了,編譯器產生的一個合成類的實例被創建和初始化。從JVM角度,就是通過這個對象持有Lambda方法的。有趣的是雖然Lambda被定義為我們方法的一個組成部分,但實際上它完全存在于我們的類之外。
new myLambdas/Lambda1$$anonfun$1 //new一個lambda實例變量.
dup //把lambda實例變量引用壓入操作棧.// 最后,調用它的構造方法.記住,對于JVM來說,它僅僅只是一個普通對象.
invokespecial myLambdas/Lambda1$$anonfun$1/()V//這兩行長的代碼加載了用于創建list的immutable.List CanBuildFrom工廠。
//這個工廠模式是Scala集合架構的一部分。
getstatic scala/collection/immutable/List$/MODULE$
Lscala/collection/immutable/List$;
invokevirtual scala/collection/immutable/List$/canBuildFrom()
Lscala/collection/generic/CanBuildFrom;// 現在我們的操作棧中已經有了Lambda對象和工廠
// 接下來的步驟是調用map函數。
// 如果你記得,我們一開始已經將names對象引用壓入操作棧頂。
// names對象現在被作為map方法調用的實例,
// 它也可以接受Lambda對象和工廠用于生成一個包含字符串長度的新集合。
invokevirtual scala/collection/immutable/List/map(Lscala/Function1;
Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;
但是,等等,Lambda對象內部到底發生了什么呢?
Lambda 對象
Lambda類衍生自scala.runtime.AbstractFunction1。通過調用map函數可以多態調用被重寫的apply方法,被重寫的apply方法代碼如下:
aload_0 //加載this對象引用到操作棧
aload_1 //加載字符串參數到操作棧
checkcast java/lang/String //檢查是不是字符串類型// 調用合成類中重寫的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)。這僅僅表明了這些結構后面的復雜性.試想Lambda表達式的代碼和復雜的東西將被編譯成復雜的執行鏈。
我預計Java8會以相同的方式實現Lambda,但出人意料的是,他們使用了另一種完全不同的方式。
Java 8 – 新的實現方式
Java8的實現,字節碼比較短,但是做的事情卻很意外。它一開始很簡單地加載names變量,并且調用它的stream方法,但它接下來做的東東就顯得很優雅了.它使用一個Java7加入的一個新指令invokeDynamic去動態地連接lambda函數的真正調用點,從而代替創建一個用于包裝lambda函數的對象.
aload_1 //加載names對象引用,壓入操作棧
//調用它的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指令. 這個是JAVA 7新加入的指令,它使得JVM限制少了,并且允許動態語言運行時綁定符號.
動態鏈接. 如果你看到invokedynamic指令,你會發現實際上沒有任何Lambda函數的引用(名為lambda$0),這是因為invokedynamic的設計方式,簡單地說就是lambda的名稱和簽名,如我們的例子-
// 一個名為Lamda$0的方法,獲得一個字符串參數并返回一個Integer對象
lambdas/Lambda1.lambda$0:(Ljava/lang/String;)Ljava/lang/Integer;
他們保存在.class文件中一個單獨的表的條目中,執行invokedynamic時會將#0參數傳給指令指針。這個新的表的確在很多年后的今天首次改變了字節碼規范的結構,這也就需要我們改編Takipi的錯誤分析引擎來配合。
The Lambda code
下面這個字節碼是真正的lambda表達式.然后就是千篇一律地、簡單地加載字符串參數,調用length方法獲得長度,并且包裝返回值.注意它是作為靜態方法編譯的,從而避免了傳遞一個額外的this對象給他,就像我們前面看到的Scala中的做法.
aload_0
invokevirtual java/lang/String.length:()
invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
areturn
invokedynamic 方式的另一個優點是,它允許我們使用map函數多態地調用這個方法,而不需要去實例化一個封裝對象或調用重寫的方法.非常酷吧!
到此,相信大家對“Scala和Java8怎么寫Lambda表達式”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。