在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式。 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nashorn。

这个新引擎旨在替代Java现有JavaScript解释器Rhino。 这为我们带来了JVM的前列,当谈到在速度与世界的V8引擎执行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);

您似乎天真无邪。 但是,请稍等...

进入字节码

我们的第一个挑战是获取JVM看到的实际字节码。 与Java和Scala的编译器具有持久性(即将.class / jar文件生成到磁盘)不同,Nashorn会编译内存中的所有内容,并将字节码直接传递给JVM。 幸运的是,我们已经有了Java代理来进行救援。 我编写了一个简单的Java代理来捕获并保留生成的字节码。 从那里开始,有一个简单的javap可以打印代码。

如果您还记得的话,我很高兴看到新的Java 8编译器如何使用Java 7中引入的invokeDynamic指令链接到Lambda函数代码。 好吧,与Nashorn一起,他们真的参加了比赛。 现在,一切都完全基于它。 看看下面。

读取字节码

invokeDynamic 。 就像我们都在同一页面上一样,Java 7中添加了invokeDynamic指令,以允许人们编写自己的动态语言来在运行时决定如何链接代码。

对于Java和Scala之类的静态语言,编译器在编译时决定要调用哪种方法(在JVM运行时的帮助下,用于多态)。 运行时链接是通过标准ClassLoader完成的,以查找类。 甚至方法重载解析之类的事情都在编译时完成。

动态与静态链接 。 不幸的是,对于本质上更具动态性的语言(并且JS是一个很好的例子),静态解析可能是不可能的。 当我们在Java中说obj.foo()时,obj类要么具有foo()方法,要么没有。 在像JS这样的语言中,它将依赖于运行时obj引用的实际对象,这是静态编译器的噩梦。 在这种情况下,编译时的链接方法行不通。 但是invokeDynamic确实可以。

InvokeDynamic允许在运行时将链接推迟回该语言的编写者,因此他们可以根据自己的语言语义来指导JVM确定要调用哪种方法。 这是双赢的局面。 JVM获得了一种链接,优化和执行的实际方法,并且语言制造商可以控制其解析度。 动态链接是我们在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的人们做了一件很酷的事情,他们没有编写自己的库来解析和链接代码, 而是继续集成了dynalink这个开源项目,该项目旨在帮助动态语言基于统一平台链接代码。 这就是为什么您在每个字符串的开头看到“ dyn:”前缀的原因。

实际流量

现在我们已经掌握了Nashorn所使用的方法,现在让我们看一下实际流程。 为了简洁起见,我删除了一些说明。 完整的代码可以在这里找到。

1.第一组指令将数组映射函数加载到脚本中。

//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;)V

2.接下来,我们分配名称数组

//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函数

//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/RecompilableScriptFunctionData

4.使用名称和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;)V

5.找到打印功能并在打印机上调用

//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函数本身被编译并作为私有函数放在与脚本相同的类中。 这与Java 8 lambda非常相似。 代码本身很简单。 我们加载字符串,找到其长度函数并调用它。

//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

奖金回合–最终的字节码

到目前为止,我们一直在处理的代码实际上并不是JVM在运行时将执行的代码。 请记住,每个invokeDynamic指令都将解析为物理字节码方法,然后JVM将其编译为机器代码并执行。

为了查看JVM运行的实际字节码,我使用了一个简单的技巧。 我在类中使用简单的Java方法调用包装了对length()的调用。 这使我可以放置一个断点,并查看JVM执行以进入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();
}

现在玩游戏。 该堆栈中有几帧? 想一想。 如果您猜不到<100 –您欠我一杯啤酒。 完整的调用堆栈可以在这里找到。

之所以如此,也是非常有趣的原因,这是关于即将发布的全新帖子的故事。

参考: 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表达式相关推荐

  1. nashorn预编译_Java 8:在新的Nashorn JS引擎中编译Lambda表达式

    nashorn预编译 在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式. 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nasho ...

  2. 项目中使用 java函数式编程_函数式编程在Java8中使用Lambda表达式进行开发

    事情起因 如果不喜欢看故事的同学,请前往下一章节. 事情起因是我打算集成Redis缓存,配置了Redis以后,发现@Cacheable注解始终不生效,折腾了大半天以后,我的心态已经崩了,决定不使用@C ...

  3. Java新特性:Java8函数式接口与Lambda表达式(一)

    摘要 何为函数式接口? 什么是lambda表达式,lambda表达式的本质: 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例: 常用Ja ...

  4. 使用NetBeans Lambda支持在Java 8中使用Lambda表达式对列表进行排序

    作为JSR 335的一部分, Lambda表达式已从Java 8开始引入Java语言,这是Java语言的一个重大变化. 如果您想了解更多关于Lambda表达式以及JSR 335的信息,可以访问以下资源 ...

  5. 如何开始使用Java中的Lambda表达式

    by Luis Santiago 路易斯·圣地亚哥(Luis Santiago) 如何开始使用Java中的Lambda表达式 (How to start working with Lambda Exp ...

  6. 如何在Java中使用Lambda表达式

    如何在Java中使用Lambda表达式 Lambda表达式是Java 8新增的一个非常强大的特性.它使得函数式编程在Java中变得更加容易和直观. Lambda表达式的基础知识 在Java中,Lamb ...

  7. Java学习记录五(多线程、网络编程、Lambda表达式和接口组成更新)

    Java学习记录五(多线程.网络编程.Lambda表达式和接口组成更新) Java 25.多线程 25.1实现多线程 25.1.1进程 25.1.2线程 25.1.3多线程的实现 25.1.4设置和获 ...

  8. Java 8中使用Lambda表达式的策略模式

    策略模式是" 设计模式:可重用对象的元素"书中的模式之一 . 本书所述的策略模式的意图是: 定义一系列算法,封装每个算法,并使它们可互换. 策略使算法独立于使用该算法的客户端而变化 ...

  9. 探索Java语言与JVM中的Lambda表达式

    2019独角兽企业重金招聘Python工程师标准>>> 转载来源:http://www.admin10000.com/document/1291.html Lambda表达式是自Ja ...

最新文章

  1. TCP网络应用程序的注意点
  2. 关于 performSelector 的一些小探讨
  3. 信息提醒之Toast-更新中
  4. 一堂儿童科学实验课引起的思考:数学和化学有什么关系?
  5. leetcode491. 递增子序列(回溯算法)
  6. webpack filemanager-webpack-plugin 打多个zip时,出现循环打包情况
  7. C#在类型实例化时都干了什么:从一道笔试题说开去
  8. onpostexecute 中更新adapter 事变_Mac音频转换器----Adapter
  9. 《人月神话》读后感一
  10. python发送cookie请求_Python中实现带Cookie的Http的Post请求
  11. Thinkpad官方推荐的驱动安装顺序
  12. 段码液晶屏实现原理和需要注意的事项
  13. 内存分配函数总结- 陈敏的博客- CSDN博客
  14. 从“傻X”到牛X——写在最前面的话
  15. 短信定位 android,短信定位
  16. 网络篇 物理拓扑与逻辑拓扑-19
  17. 西南大学计算机学院读博咋样,专硕想读博士,难吗?北大学长如是说!
  18. 查看Python的安装目录
  19. Discuz首页最后发表帖子地址伪静态
  20. 区块链零售市场价值到2023年将飙升29倍

热门文章

  1. MyBatisPlus(笔记)
  2. OpenCV3.4.5带GPU编译error: #error This file requires compiler and library support for the ISO C++ 2011
  3. python快捷方式图标_python – PyInstaller无法更改快捷方式图标
  4. 车提示检测轮胎气压_水淹车估价中心_辽宁中车检
  5. MySQL优化(四):count()
  6. 注解代替枚举_精选枚举代替开关
  7. feign rest_与Feign客户轻松进行REST通信
  8. 带有Prometheus的弹簧靴和千分尺第5部分:旋转Prometheus
  9. Spring Boot应用程序浪费了内存
  10. 回调函数中有回调函数吗_嗨,那里有回调!