javascript
Java 8:在新的Nashorn JS引擎中编译Lambda表达式
在最近的一篇文章中,我了解了Java 8和Scala如何實現(xiàn)Lambda表達式。 眾所周知,Java 8不僅引入了對Javac編譯器的改進,而且還引入了全新的解決方案-Nashorn。
這個新引擎旨在替代Java現(xiàn)有JavaScript解釋器Rhino。 這為我們帶來了JVM的前列,當談到在速度與世界的V8引擎執(zhí)行JavaScript,在那里(我希望我們會最終得到過去那種汽車地毯的事情 :))所以,我認為這將是一個很好的時機,也可以通過深入了解Nashorn,看看它如何編譯Lambda表達式(尤其是與Java和Scala相比)。
我們將要看的lambda表達式類似于我們用Java和Scala測試過的表達式。
這是代碼:
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("nashorn");String js;js = "var map = Array.prototype.map \n"; js += "var names = [\"john\", \"jerry\", \"bob\"]\n"; js += "var a = map.call(names, function(name) { return name.length() })\n"; js += "print(a)";engine.eval(js);您似乎天真無邪。 但是,請稍等...
進入字節(jié)碼
我們的第一個挑戰(zhàn)是獲取JVM看到的實際字節(jié)碼。 與Java和Scala的編譯器具有持久性(即將.class / jar文件生成到磁盤)不同,Nashorn會編譯內存中的所有內容,并將字節(jié)碼直接傳遞給JVM。 幸運的是,我們已經有了Java代理來進行救援。 我編寫了一個簡單的Java代理來捕獲并保留生成的字節(jié)碼。 從那里開始,有一個簡單的javap可以打印代碼。
如果您還記得的話,我很高興看到新的Java 8編譯器如何使用Java 7中引入的invokeDynamic指令鏈接到Lambda函數(shù)代碼。 好吧,與Nashorn一起,他們真的參加了比賽。 現(xiàn)在,一切都完全基于它。 看看下面。
讀取字節(jié)碼
invokeDynamic 。 就像我們都在同一頁面上一樣,Java 7中添加了invokeDynamic指令,以允許人們編寫自己的動態(tài)語言來在運行時決定如何鏈接代碼。
對于Java和Scala之類的靜態(tài)語言,編譯器在編譯時決定要調用哪種方法(在JVM運行時的幫助下,用于多態(tài))。 運行時鏈接是通過標準ClassLoader完成的,以查找類。 甚至方法重載解析之類的事情都在編譯時完成。
動態(tài)與靜態(tài)鏈接 。 不幸的是,對于本質上更具動態(tài)性的語言(并且JS是一個很好的例子),靜態(tài)解析可能是不可能的。 當我們在Java中說obj.foo()時,obj類要么具有foo()方法,要么沒有。 在像JS這樣的語言中,它將依賴于運行時obj引用的實際對象,這是靜態(tài)編譯器的噩夢。 在這種情況下,編譯時的鏈接方法行不通。 但是invokeDynamic確實可以。
InvokeDynamic允許在運行時將鏈接推遲回該語言的編寫者,因此他們可以根據自己的語言語義來指導JVM確定要調用哪種方法。 這是雙贏的局面。 JVM獲得了一種鏈接,優(yōu)化和執(zhí)行的實際方法,并且語言制造商可以控制其解析度。 動態(tài)鏈接是我們在Takipi中必須努力支持的工作 。
Nashorn如何鏈接
Nashorn確實有效地利用了這一點。 讓我們看一下示例,以了解其工作原理。 這是Lambda代碼中的第一條invokeDynamic指令,用于檢索JS Array類的值–
invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;Nashorn要求JVM在運行時將其傳遞給該字符串,作為交換,它將向一個接受對象并返回一個對象的方法返回一個句柄。 只要JVM獲得了這種方法的句柄,它就可以鏈接。
.class文件的特殊部分中指定了負責返回此句柄的方法(也稱為引導程序方法),該文件中包含可用的引導程序方法列表。 您看到的0值是該方法在該表中的索引,JVM將調用該索引以獲取將鏈接到的方法句柄。
在我看來,Nashorn的人們做了一件很酷的事情,他們沒有編寫自己的庫來解析和鏈接代碼, 而是繼續(xù)集成了dynalink這個開源項目,該項目旨在幫助動態(tài)語言基于統(tǒng)一平臺鏈接代碼。 這就是為什么您在每個字符串的開頭看到“ dyn:”前綴的原因。
實際流量
現(xiàn)在我們已經掌握了Nashorn所使用的方法,現(xiàn)在讓我們看一下實際流程。 為了簡潔起見,我刪除了一些說明。 完整的代碼可以在這里找到。
1.第一組指令將數(shù)組映射函數(shù)加載到腳本中。
//load JS array invokedynamic 0 "dyn:getProp|getElem|getMethod:Array":(Ljava/lang/Object;)Ljava/lang/Object;//load its prototype element invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;//load the map method invokedynamic 0 "dyn:getProp|getElem|getMethod:map":(Ljava/lang/Object;)Ljava/lang/Object;//set it to the map local invokedynamic 0 #0:"dyn:setProp|setElem:map":(Ljava/lang/Object;Ljava/lang/Object;)V2.接下來,我們分配名稱數(shù)組
//allocate the names array as a JS object invokestatic jdk/nashorn/internal/objects/Global.allocate:([Ljava/lang/Object;)Ljdk/nashorn/internal/objects/NativeArray;//places it into names invokedynamic 0 #0:"dyn:setProp|setElem:names":(Ljava/lang/Object;Ljava/lang/Object;)Vinvokedynamic 0 #0:"dyn:getProp|getElem|getMethod:names":(Ljava/lang/Object;)Ljava/lang/Object;3.查找并加載Lambda函數(shù)
//load the constants field for this script compiled and filled at runtime by Nashorn getstatic constants//refer to the 2nd entry, where Nashorn will place a handle to the lambda code iconst_2//get it from the constants array aaload//ensure it’s a JS function object checkcast class jdk/nashorn/internal/runtime/RecompilableScriptFunctionData4.使用名稱和Lambda調用map,然后將結果放入
//call the map function, passing it names and the Lambda function from the stackinvokedynamic 0 #1:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljdk/nashorn/internal/runtime/ScriptFunction ;)Ljava/lang/Object;//put the result in a invokedynamic 0 #0:"dyn:setProp|setElem:a":(Ljava/lang/Object;Ljava/lang/Object;)V5.找到打印功能并在打印機上調用
//load the print function invokedynamic 0 #0:"dyn:getMethod|getProp|getElem:print":(Ljava/lang/Object;)Ljava/lang/Object;//load a invokedynamic 0 #0:"dyn:getProp|getElem|getMethod:a":(Ljava/lang/Object;)Ljava/lang/Object;// call print on it invokedynamic 0 #2:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;lambda函數(shù)本身被編譯并作為私有函數(shù)放在與腳本相同的類中。 這與Java 8 lambda非常相似。 代碼本身很簡單。 我們加載字符串,找到其長度函數(shù)并調用它。
//Load the name argument (var #1) aload_1//find its length() function invokedynamic 0 "dyn:getMethod|getProp|getElem:length":(Ljava/lang/Object;)Ljava/lang/Object;//call length invokedynamic 0 "dyn:call":(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;//return the result areturn獎金回合–最終的字節(jié)碼
到目前為止,我們一直在處理的代碼實際上并不是JVM在運行時將執(zhí)行的代碼。 請記住,每個invokeDynamic指令都將解析為物理字節(jié)碼方法,然后JVM將其編譯為機器代碼并執(zhí)行。
為了查看JVM運行的實際字節(jié)碼,我使用了一個簡單的技巧。 我在類中使用簡單的Java方法調用包裝了對length()的調用。 這使我可以放置一個斷點,并查看JVM執(zhí)行以進入Lambda的最終調用堆棧。
這是代碼–
js += "var a = map.call(names, function(name) { return Java.type("LambdaTest”).wrap(name.length()) })";包好了–
public static int wrap(String s) { return s.length(); }現(xiàn)在玩游戲。 該堆棧中有幾幀? 想一想。 如果您猜不到<100 –您欠我一杯啤酒。 完整的調用堆??梢栽谶@里找到。
之所以如此,也是非常有趣的原因,這是關于即將發(fā)布的全新帖子的故事。
參考: Java 8:來自Takipi博客的JCG合作伙伴 Iris Shoor 在New Nashorn JS Engine中編譯Lambda表達式 。翻譯自: https://www.javacodegeeks.com/2014/02/java-8-compiling-lambda-expressions-in-the-new-nashorn-js-engine.html
總結
以上是生活随笔為你收集整理的Java 8:在新的Nashorn JS引擎中编译Lambda表达式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 选择通过更改内容类型返回的详细程度,第二
- 下一篇: 具有Spring Boot的Spring