Java8之使用新JS解释器Nashorn编译Lambda表达式

简介:
  作者: Tal Weiss  CEO of  Takipi   译者:踏雁寻花,xbkaishui  校对:方腾飞

在最近的一篇文章中,我了解了一下Java8和Scala是如何实现 Lambda 表达式的。正如我们所知道的,Java8不仅对javac编辑器做了很大改进,它还加入了一个全新的项目—Nashorn。这个新的解释器将会代替Java现有的Rhino解释器。据说它执行JavaScript的速度非常之快,就像世界上最快的跑车 V8s,所以,我觉得现在很有必要打开Nashorn源码,看看它是如何编译 Lambda 表达式的(着重于Java 和 Scala的对比)。

我们使用Java和Scala测试的 lambda表达式是非常相似的。

代码如下:

01 jcriptEngineManager manager = new ScriptEngineManager();
02 ScriptEngine engine = manager.getEngineByName("nashorn");
03  
04 String js;
05  
06 js = "var map = Array.prototype.map \n";
07 js += "var names = [\"john\", \"jerry\", \"bob\"]\n";
08 js += "var a = map.call(names, function(name) { return name.length() })\n";
09 js += "print(a)";
10  
11 engine.eval(js);

感觉有点儿懵吧,继续往下看…

获取字节码

我们第一个任务就是获取JVM可以看懂的字节码。与Java和Scala编译器不同,这两个编译器是持久的(产生的.class文件、jar文件存放到磁盘),而Nashorn解释器则不同,Nashorn 编译后的数据都在内存中,然后把字节码支持传给JVM。我写了一个简单的Java代理来获得并保存生成的字节码,其实就是一个简单的javap反编译器了。

我看到Java8编译器使用了 invokeDynamic指令感到特别激动, invokeDynamic指令是在Java7中被引用的,目的是调用 Lambda函数。现在基于 Nashorn的工作都已经做完了,继续往下看。

读取字节码

invokeDynamic 指令:这个指令和我们整篇文章密切相关。Java 7 引入invokeDynamic 指令的目的是为了让开发人员可以自己去编写动态语言,决定在运行时如何链接代码,

对于像Java和Scala这样的静态语言来说,编译器在编译的时候就决定了哪一个方法将会被调用(而Java的多态性是通过JVM的一些的工具实现的),运行时的链接是通过 ClassLoaders加载类来完成的,甚至方法重载都是在编译时期完成的。

动态链接 VS 静态链接:很不幸,对于动态语言来说,静态解析也许是不可能的(JS就是一个很好的例子),当我们在Java语言中执行 obj.foo() 方法时,obj对象的类中也许有foo()方法,也许没有,而在一个类似JS的语言中,则取决于运行时obj实际对象的引用—静态编译器的噩梦。编译时链接在这个时候根本不起作用,不过 invokeDynamic指令可以做到。

InvokeDynamic 指令可以在运行时推迟返回这个语言的开发者的链接,所以它们能够根据自己的语义引导JVM调用哪一个方法,这是一个双赢的方案。JVM可以获得一个实际的链接方法,并进行优化,执行,而且语言开发者可以控制自己的解析方案。在Takipi这个网站中我们必须努力去支持动态链接。

Nashorn解释器如何链接:Nashorn很好的利用了这一点。让我们看一看一个例子来理解Nashorn是如何工作的。代码的作用是用来检索JS数组类的值:

invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;

Nashorn需要JVM在运行时传递一个String类型参数,并返回一个方法,这个方法接受一个Object类型的参数,同时返回一个Object类型的对象。只要JVM获得这个方法的一个句柄(handle),就会链接。

这个方法负责返回一个句柄(就是一个引导程序的方法–bootstrap method),在.class文件中的一个特殊部分被指定,持有一系列的引导方法。你看到的0是表的索引,JVM调用方法获得方法的句柄,JVM就是用这个句柄进行链接的。

我认为Nashorn项目开发团队做了一件很爽的事情,那就是不再需要他们自己编写解析和链接代码的库了,而是集成了dynalink项目,这个开源项目是为了在一个统一的平台上将动态语言链接成代码。这就是为什么在每一个String之前都有一个”dyn:”前缀的原因了。

实际的工作流

既然我们已经完成了Nashorn所使用的方法,下面就让我们看一看实际流。为了简洁,我去掉了一些不重要的代码。整个代码可以在这里下载。 1、这段儿代码作用是加载JS数组函数映射到脚本中

//加载JS数组(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;

//加载map方法(load the map method)
invokedynamic 0 "dyn:getProp|getElem|getMethod:map":(Ljava/lang/Object;)Ljava/lang/Object;

//set到本地(set it to the map local)
invokedynamic 0 #0:"dyn:setProp|setElem:map":(Ljava/lang/Object;Ljava/lang/Object;)V

2、分配names 数组

//把names数组分成JS对象(allocate the names array as a JS object)
invokestatic jdk/nashorn/internal/objects/Global.allocate:([Ljava/lang/Object;)Ljdk/nashorn/internal/objects/NativeArray;

//将对象放到names中(places it into names)
invokedynamic 0 #0:"dyn:setProp|setElem:names":(Ljava/lang/Object;Ljava/lang/Object;)V

invokedynamic 0 #0:"dyn:getProp|getElem|getMethod:names":(Ljava/lang/Object;)Ljava/lang/Object;

3、找到并加载Lambda 函数

//为在运行时被Nashorn编译的脚本加载常量(load the constants field for this script compiled and filled at runtime by Nashorn)
getstatic constants

//将2放到栈顶,Nashorn将会把句柄放到lambda代码中(refer to the 2nd entry, where Nashorn will place a handle to the lambda code)
iconst_2

//从常量数组中获取它(get it from the constants array)
aaload

//检察它是否是一个JS函数对象(ensure it’s a JS function object)
checkcast class jdk/nashorn/internal/runtime/RecompilableScriptFunctionData

4、通过传入参数names和Lambda调用map函数,把结果存放到a中

//调用map函数,把names和栈中返回的Lambda函数当做参数传入(call the map function, passing it names and the Lambda function from the stack)
invokedynamic 0 #1:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljdk/nashorn/internal/runtime/ScriptFunction;)Ljava/lang/Object;

//把返回结果存放到a中(put the result in a)
invokedynamic 0 #0:"dyn:setProp|setElem:a":(Ljava/lang/Object;Ljava/lang/Object;)V

5、找到print函数,并用a调用它

//加载print函数(load the print function)
invokedynamic 0 #0:"dyn:getMethod|getProp|getElem:print":(Ljava/lang/Object;)Ljava/lang/Object;

//加载a(load a)
invokedynamic 0 #0:"dyn:getProp|getElem|getMethod:a":(Ljava/lang/Object;)Ljava/lang/Object;

//调用print函数(call print on it)
invokedynamic 0 #2:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

lambda函数和脚本一样被编译并放到相同的类中,作为一个private方法。这个和Java8中lambdas表达式是非常相似的。代码非常简单,我们加载String,并找到lengh()方法,然后调用它。

//加载参数名称(Load the name argument (var #1))
aload_1

//找到length()方法(find its length() function)
invokedynamic 0 "dyn:getMethod|getProp|getElem:length":(Ljava/lang/Object;)Ljava/lang/Object;

//调用length()(call length)
invokedynamic 0 "dyn:call":(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

//返回结果(return the result)
areturn

奖励环节-最后的字节码

到目前为止,我们所完成的代码不能在JVM运行时执行。要记住,每一个invokeDynamic 指令将会被处理成一个物理字节码方法,然后由JVM将其编译成机器语言并执行。 为了看到JVM执行的真正字节码,我使用了一个技巧,我在类中使用一个简单的方法wrap(String s)去调用length()方法。这就需要我放一个断点,这样就可以看到JVM执行时的堆栈情况。

代码如下: js += “var a = map.call(names, function(name) { return Java.type(“LambdaTest”).wrap(name.length()) })”;

这是wrap方法: public static int wrap(String s) { return s.length(); }

文章转自 并发编程网-ifeve.com

目录
相关文章
|
9天前
|
缓存 监控 Java
深入解析java正则表达式
本文深入解析Java正则表达式的应用,从基础概念到实际开发技巧全面展开。正则表达式是一种强大的文本处理工具,广泛应用于格式验证、搜索替换等场景。Java通过`Pattern`和`Matcher`类支持正则表达式,`Pattern.compile()`方法将正则字符串编译为高效模式对象。文章详细介绍了核心类的功能、常用正则语法及实际案例(如邮箱和电话号码验证)。掌握这些内容,可显著提升文本处理能力,满足多种开发需求。
39 1
|
9天前
|
Java 编译器 API
Java Lambda 表达式:以 Foo 接口为例深入解析
本文深入解析了 Java 8 中 Lambda 表达式的用法及其背后的函数式接口原理,以 `Foo` 接口为例,展示了如何通过简洁的 Lambda 表达式替代传统匿名类实现。文章从 Lambda 基本语法、函数式接口定义到实际应用层层递进,并探讨默认方法与静态方法的扩展性,最后总结常见误区与关键点,助你高效优化代码!
29 0
|
2月前
|
前端开发 JavaScript Java
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
174 13
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
|
2月前
|
人工智能 JavaScript 关系型数据库
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
128 14
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
|
1月前
|
SQL Rust Java
怎么理解Java中的lambda表达式
Lambda表达式是JDK8引入的新语法,用于简化匿名内部类的代码写法。其格式为`(参数列表) -> { 方法体 }`,适用于函数式接口(仅含一个抽象方法的接口)。通过Lambda表达式,代码更简洁灵活,提升Java的表达能力。
|
2月前
|
SQL JavaScript 安全
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
127 11
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
|
1月前
|
SQL IDE 算法
《从头开始学java,一天一个知识点》之:运算符与表达式:算术、比较和逻辑运算
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问"`a==b`和`equals()`的区别",大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列为你打造Java「速效救心丸」,每天1分钟,地铁通勤、午休间隙即可完成学习。直击高频考点和实际开发中的「坑位」,拒绝冗长概念,每篇都有可运行的代码示例。明日预告:《控制流程:if-else条件语句实战》。
38 6
|
2月前
|
人工智能 JavaScript 安全
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
138 13
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
|
4月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
4月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。