Java 8 Nashorn 教程
译者:飞龙
这个教程中,你会通过简单易懂的代码示例,来了解Nashorn JavaScript引擎。Nashorn JavaScript引擎是Java SE 8 的一部分,并且和其它独立的引擎例如Google V8(用于Google Chrome和Node.js的引擎)互相竞争。Nashorn通过在JVM上,以原生方式运行动态的JavaScript代码来扩展Java的功能。
在接下来的15分钟内,你会学到如何在JVM上在运行时动态执行JavaScript。我会使用小段代码示例来演示最新的Nashron语言特性。你会学到如何在Java代码中调用JavaScript函数,或者相反。最后你会准备好将动态脚本集成到你的Java日常业务中。
更新 - 我现在正在编写用于浏览器的Java8数据流API的JavaScript实现。如果你对此感兴趣,请在Github上访问Stream.js。非常期待你的反馈。
使用 Nashron
Nashorn JavaScript引擎可以在Java代码中编程调用,也可以通过命令行工具jjs
使用,它在$JAVA_HOME/bin
中。如果打算使用jjs
,你可能希望设置符号链接来简化访问:
$ cd /usr/bin $ ln -s $JAVA_HOME/bin/jjs jjs $ jjs jjs> print('Hello World');
这个教程专注于在Java代码中调用Nashron,所以让我们先跳过jjs
。Java代码中简单的HelloWorld如下所示:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); engine.eval("print('Hello World!');");
为了在Java中执行JavaScript,你首先要通过 Nashorn JavaScript基于ECMAScript 5.1,但是它的后续版本会对ES6提供支持: Nashorn的当前策略遵循ECMAScript规范。当我们在JDK8中发布它时,它将基于ECMAScript 5.1。Nashorn未来的主要发布基于ECMAScript 6。 Nashorn定义了大量对ECMAScript标准的语言和API扩展。但是首先让我们看一看Java和JavaScript代码如何交互。 Nashorn 支持从Java代码中直接调用定义在脚本文件中的JavaScript函数。你可以将Java对象传递为函数参数,并且从函数返回数据来调用Java方法。 下面的JavaScript函数稍后会在Java端调用: 为了调用函数,你首先需要将脚本引擎转换为 执行这段代码会在控制台产生三行结果。调用函数 现在让我们通过传入任意Java对象来调用第二个函数: Java对象在传入时不会在JavaScript端损失任何类型信息。由于脚本在JVM上原生运行,我们可以在Nashron上使用Java API或外部库的全部功能。 在JavaScript中调用Java方法十分容易。我们首先需要定义一个Java静态方法。 Java类可以通过 在使用JavaScript原生类型调用Java方法时,Nashorn 如何处理类型转换?让我们通过简单的例子来弄清楚。 下面的Java方法简单打印了方法参数的实际类型: 为了理解背后如何处理类型转换,我们使用不同的JavaScript类型来调用这个方法: JavaScript原始类型转换为合适的Java包装类,而JavaScript原生对象会使用内部的适配器类来表示。要记住 任何标记为“内部”的东西都可能会从你那里发生改变。 在向Java传递原生JavaScript对象时,你可以使用 下面的例子将参数类型从 当向这个方法传递对象(哈希表)时,在Java端可以访问其属性: 我们也可以在Java中调用JavaScript的成员函数。让我们首先定义JavaScript JavaScript方法 当向Java方法传递新的 Nashorn定义了多种对ECMAScript标准的语言和API扩展。让我们看一看最新的特性: JavaScript的原生数组是无类型的。Nashron允许你在JavaScript中使用Java的类型数组: 我们可以使用任何Java集合,而避免使用数组瞎折腾。首先需要通过 为了迭代集合和数组,Nashron引入了 下面是另一个集合的范围遍历示例,使用 每个人都热爱lambda和数据流 -- Nashron也一样!虽然ECMAScript 5.1没有Java8 lmbda表达式的简化箭头语法,我们可以在任何接受lambda表达式的地方使用函数字面值。 Java类型可以由 方法和函数可以通过点运算符或方括号运算符来调用: 当使用重载参数调用方法时,传递可选参数类型 你可以简单地使用属性名称来向Java Beans获取或设置值,不需要显式调用读写器: 对于简单的单行函数,我们可以去掉花括号: 两个不同对象的属性可以绑定到一起: 我喜欢去掉空白的字符串: 以防你忘了自己在哪里: 有时一次导入多个Java包会很方便。我们可以使用 一些类似 下面的代码将Java列表转换为JavaScript原生数组: 下面的代码执行相反操作: 在JavaScript中访问被覆盖的成员通常比较困难,因为Java的 首先我们需要在Java代码中定义超类: 下面我在JavaScript中覆盖了 我们通过 在JavaScript中加载额外的脚本文件非常方便。我们可以使用 我在我的Web前端中大量使用 外部脚本会在相同JavaScript上下文中被执行,所以我们可以直接访问underscore 的对象。要记住当变量名称互相冲突时,脚本的加载可能会使你的代码崩溃。 这一问题可以通过把脚本文件加载到新的全局上下文来绕过: 如果你对编写命令行(shell)脚本感兴趣,来试一试Nake吧。Nake是一个Java 8 Nashron的简化构建工具。你只需要在项目特定的 对Java开发者来说,编写命令行脚本是前所未有的简单... 我希望这个教程对你有所帮助,并且你能够享受Nashron JavaScript引擎之旅。有关Nashron的更多信息,请见这里、这里和这里。使用Nashron编写shell脚本的教程请见这里。 我最近发布了一篇后续文章,关于如何在Nashron中使用Backbone.js模型。如果你想要进一步学习Java8,请阅读我的Java8教程,和我的Java8数据流教程。 这篇Nashron教程中的可运行的源代码托管在Github上。请随意fork我的仓库,或者在Twitter上向我反馈。javax.script
包创建脚本引擎。这个包已经在在Java中调用JavaScript函数
var fun1 = function(name) { print('Hi there from Javascript, ' + name); return "greetings from javascript";
};var fun2 = function (object) { print("JS Class Definition: " + Object.prototype.toString.call(object));
};
Invocable
。Invocable
接口由NashornScriptEngine
实现,并且定义了invokeFunction
方法来调用指定名称的JavaScript函数。ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("fun1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());// Hi there from Javascript, Peter Parker// greetings from javascript// class java.lang.String
print
将结果输出到System.out
,所以我们会首先看到JavaScript输出。invocable.invokeFunction("fun2", new Date());// [object java.util.Date]invocable.invokeFunction("fun2", LocalDateTime.now());// [object java.time.LocalDateTime]invocable.invokeFunction("fun2", new Person());// [object com.winterbe.java8.Person]
在JavaScript中调用Java方法
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name); return "greetings from java";
}
Java.type
API扩展在JavaScript中引用。它就和Java代码中的import
类似。只要定义了Java类型,我们就可以自然地调用静态方法fun1()
,然后像sout
打印信息。由于方法是静态的,我们不需要首先创建实例。var MyJavaClass = Java.type('my.package.MyJavaClass');var result = MyJavaClass.fun1('John Doe');print(result);// Hi there from Java, John Doe// greetings from java
static void fun2(Object object) {
System.out.println(object.getClass());
}
MyJavaClass.fun2(123);// class java.lang.IntegerMyJavaClass.fun2(49.99);// class java.lang.DoubleMyJavaClass.fun2(true);// class java.lang.BooleanMyJavaClass.fun2("hi there")// class java.lang.StringMyJavaClass.fun2(new Number(23));// class jdk.nashorn.internal.objects.NativeNumberMyJavaClass.fun2(new Date());// class jdk.nashorn.internal.objects.NativeDateMyJavaClass.fun2(new RegExp());// class jdk.nashorn.internal.objects.NativeRegExpMyJavaClass.fun2({foo: 'bar'});// class jdk.nashorn.internal.scripts.JO4
jdk.nashorn.internal
中的类可能会有所变化,所以不应该在客户端面向这些类来编程。ScriptObjectMirror
ScriptObjectMirror
类,它实际上是底层JavaScript对象的Java表示。ScriptObjectMirror
实现了Map
接口,位于jdk.nashorn.api
中。这个包中的类可以用于客户端代码。Object
改为ScriptObjectMirror
,所以我们可以从传入的JavaScript对象中获得一些信息。static void fun3(ScriptObjectMirror mirror) {
System.out.println(mirror.getClassName() + ": " +
Arrays.toString(mirror.getOwnKeys(true)));
}
MyJavaClass.fun3({
foo: 'bar',
bar: 'foo'});// Object: [foo, bar]
Person
类型,带有属性firstName
和 lastName
,以及方法getFullName
。function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = function() { return this.firstName + " " + this.lastName;
}
}
getFullName
可以通过callMember()
在ScriptObjectMirror
上调用。static void fun4(ScriptObjectMirror person) {
System.out.println("Full Name is: " + person.callMember("getFullName"));
}
Person
时,我们会在控制台看到预期的结果:var person1 = new Person("Peter", "Parker");
MyJavaClass.fun4(person1);// Full Name is: Peter Parker
语言扩展
类型数组
var IntArray = Java.type("int[]");var array = new IntArray(5);array[0] = 5;array[1] = 4;array[2] = 3;array[3] = 2;array[4] = 1;try { array[5] = 23;
} catch (e) { print(e.message); // Array index out of range: 5}array[0] = "17";print(array[0]); // 17array[0] = "wrong type";print(array[0]); // 0array[0] = "17.3";print(array[0]); // 17
int[]
数组就像真实的Java整数数组那样。但是此外,在我们试图向数组添加非整数时,Nashron在背后执行了一些隐式的转换。字符串会自动转换为整数,这十分便利。集合和范围遍历
Java.type
定义Java类型,之后创建新的实例。var ArrayList = Java.type('java.util.ArrayList');var list = new ArrayList();list.add('a');list.add('b');list.add('c');for each (var el in list) print(el); // a, b, c
for each
语句。它就像Java的范围遍历那样工作。HashMap
:var map = new java.util.HashMap();
map.put('foo', 'val1');
map.put('bar', 'val2');for each (var e in map.keySet()) print(e); // foo, barfor each (var e in map.values()) print(e); // val1, val2
Lambda表达式和数据流
var list2 = new java.util.ArrayList();
list2.add("ddd2");
list2.add("aaa2");
list2.add("bbb1");
list2.add("aaa1");
list2.add("bbb3");
list2.add("ccc");
list2.add("bbb2");
list2.add("ddd1");
list2
.stream()
.filter(function(el) { return el.startsWith("aaa");
})
.sorted()
.forEach(function(el) { print(el);
}); // aaa1, aaa2
类的继承
Java.extend
轻易扩展。就像你在下面的例子中看到的那样,你甚至可以在你的脚本中创建多线程的代码:var Runnable = Java.type('java.lang.Runnable');var Printer = Java.extend(Runnable, {
run: function() { print('printed from a separate thread');
}
});var Thread = Java.type('java.lang.Thread');new Thread(new Printer()).start();new Thread(function() { print('printed from another thread');
}).start();// printed from a separate thread// printed from another thread
参数重载
var System = Java.type('java.lang.System');
System.out.println(10); // 10System.out["println"](11.0); // 11.0System.out["println(double)"](12); // 12.0
println(double)
会指定所调用的具体方法。Java Beans
var Date = Java.type('java.util.Date');var date = new Date();
date.year += 1900;
print(date.year); // 2014
函数字面值
function sqr(x) x * x;print(sqr(3)); // 9
属性绑定
var o1 = {};var o2 = { foo: 'bar'};
Object.bindProperties(o1, o2);print(o1.foo); // baro1.foo = 'BAM';print(o2.foo); // BAM
字符串去空白
print(" hehe".trimLeft()); // heheprint("hehe ".trimRight() + "he"); // hehehe
位置
print(__FILE__, __LINE__, __DIR__);
导入作用域
JavaImporter
类,和with
语句一起使用。所有被导入包的类文件都可以在with
语句的局部域中访问到。var imports = new JavaImporter(java.io, java.lang);with (imports) { var file = new File(__FILE__);
System.out.println(file.getAbsolutePath()); // /path/to/my/script.js}
数组转换
java.util
的包可以不使用java.type
或JavaImporter
直接访问:var list = new java.util.ArrayList();list.add("s1");list.add("s2");list.add("s3");
var jsArray = Java.from(list);print(jsArray); // s1,s2,s3print(Object.prototype.toString.call(jsArray)); // [object Array]
var javaArray = Java.to([3, 5, 7, 11], "int[]");
访问超类
super
关键字在ECMAScript中并不存在。幸运的是,Nashron有一套补救措施。class SuperRunner implements Runnable { @Override
public void run() {
System.out.println("super run");
}
}
SuperRunner
。要注意创建新的Runner
实例时的Nashron语法:覆盖成员的语法取自Java的匿名对象。var SuperRunner = Java.type('com.winterbe.java8.SuperRunner');var Runner = Java.extend(SuperRunner);var runner = new Runner() {
run: function() {
Java.super(runner).run(); print('on my run');
}
}
runner.run();// super run// on my run
Java.super()
扩展调用了被覆盖的SuperRunner.run()
方法。加载脚本
load
函数加载本地或远程脚本。loadWithNewGlobal('script.js');
命令行脚本
Nakefile
中定义任务,之后通过在命令行键入nake -- myTask
来执行这些任务。任务编写为JavaScript,并且在Nashron的脚本模式下运行,所以你可以使用你的终端、JDK8 API和任意Java库的全部功能。到此为止
作者:ApacheCN_飞龙
链接:https://www.jianshu.com/p/467aaf5254f8
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章