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(); }

堆栈的调用完整情况请看这里

时间: 2024-08-15 05:10:03

Java8之使用新JS解释器Nashorn编译Lambda表达式的相关文章

编译Lambda表达式: Scala和Java 8

原文地址,译文地址,译者:梁海舰, 校对:丁一 最近几年Lambda表达式风靡于编程界. 很多现代编程语言都把它作为函数式编程的基本组成部分. 基于JVM的编程语言如Scala,Groovy还有Clojure把它们作为关键部分集成在语言中.现在Java8也加入了它们的行列. 有趣的是,对于JVM来说,Lambda表达式是完全不可见的,并没有匿名函数和Lamada表达式的概念,它只知道字节码是严格面向对象规范的.它取决于语言的作者和它的编译器在规范限制内创造出更新,更高级的语言元素. 我们第一次接

Java8:Lambdas(二)学习怎样去使用lambda表达式

原文链接  作者:Ted Neward   译者:赵峰 Java SE 8的发布很快就到了.伴随着它来的不仅仅是新的语言lambda表达式(同样被称为闭包或匿名方法)--伴随着一些语言特性支持--更重要的是API和library的增强将会使传统的Java核心libraries变的更易于使用.其中大多数的增强和补充是在Collections API中,因为Collections API在整个应用中随处可见,这篇文章大部分是在讨论它. 然而 ,很有可能大多数的Java开发者将不会熟悉隐藏在lambd

详解 Weex JS Framework 的编译过程

之前写了一篇文章<Weex 框架中 JS Framework 的结构>概述了 JS Framework 的整体结构,其中编译过程写的有些简略,这里再详细介绍一下. 一句话概括 JS Framework 的编译过程就是: 将 JS Bundle 转换成 Virtual DOM 发送到原生模块渲染. 这个过程涉及三种数据类型:JS Bundle .Virtual DOM .Vm . JS Bundle 是由 .we 文件转换过来的,会被视为代码而执行. Virtual DOM 是描述页面结构的 J

JAVA8 十大新特性详解_java

"Java is still not dead-and people are starting to figure that out." 本教程将用带注释的简单代码来描述新特性,你将看不到大片吓人的文字. 一.接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: 复制代码 代码如下: interface Formula {    double calculate(int a);     defaul

xml-求大神帮忙看看,新定义的style编译时一直提示找不到符号是怎么回事?

问题描述 求大神帮忙看看,新定义的style编译时一直提示找不到符号是怎么回事? 在frameworks/base/core/res/res/values下的styles中新定义了一个style,并在frameworks/base/core/java/android/webkit/JsDialogHelper.java中使用,但是编译的时候一直报错,找不到符号,是为什么?求各位大神帮忙看看-- 解决方案 代码如下:很简单的代码-- styles文件中:<br> <item name=&q

JS的预编译和执行顺序 详析

原文:JS的预编译和执行顺序 详析 最近在复习javascript的事件处理时发现了一个问题,然后也是我来写javascript的预编译和执行顺序的问题   代码:   复制代码 代码一 <html>   <head>     <title>事件处理</title>     <meta http-equiv="content-type" content="text/html;charset=utf-8"/>

js对象根据属性名表达式解析成新的对象

问题描述 js对象根据属性名表达式解析成新的对象 如今有一个对象如下 var obj = { "name": "zhansan", "age": 21, "parent.name": "lisi", "parent.age": 44, "girlfriend[0].name": "fanbinbin", "girlfriend[0].ag

Ember.js 模板预编译的学习笔记

Ember.js 里,如果把模板直接写在 index.html 文件中,是这样写的:  代码如下 复制代码 <script type="text/x-handlebars" data-template-name="application"> // 最顶级的模板 ... {{outlet}} </script> <script type="text/x-handlebars" data-template-name=&q

Java8新特性之Lambda表达式浅析_java

说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是