资讯专栏INFORMATION COLUMN

[译] Java 8 Nashorn 教程

_ivan / 2126人阅读

摘要:未来的主要发布基于。在中调用函数支持从代码中直接调用定义在脚本文件中的函数。下面的函数稍后会在端调用为了调用函数,你首先需要将脚本引擎转换为。调用函数将结果输出到,所以我们会首先看到输出。幸运的是,有一套补救措施。

原文:Java 8 Nashorn Tutorial
译者:飞龙
协议:CC BY-NC-SA 4.0

这个教程中,你会通过简单易懂的代码示例,来了解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,你首先要通过javax.script包创建脚本引擎。这个包已经在Rhino(来源于Mozilla、Java中的遗留JS引擎)中使用了。

JavaScript代码既可以通过传递JavaScript代码字符串,也可以传递指向你的JS脚本文件的FileReader来执行:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));

Nashorn JavaScript基于ECMAScript 5.1,但是它的后续版本会对ES6提供支持:

Nashorn的当前策略遵循ECMAScript规范。当我们在JDK8中发布它时,它将基于ECMAScript 5.1。Nashorn未来的主要发布基于ECMAScript 6。

Nashorn定义了大量对ECMAScript标准的语言和API扩展。但是首先让我们看一看Java和JavaScript代码如何交互。

在Java中调用JavaScript函数

Nashorn 支持从Java代码中直接调用定义在脚本文件中的JavaScript函数。你可以将Java对象传递为函数参数,并且从函数返回数据来调用Java方法。

下面的JavaScript函数稍后会在Java端调用:

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

为了调用函数,你首先需要将脚本引擎转换为InvocableInvocable接口由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输出。

现在让我们通过传入任意Java对象来调用第二个函数:

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]

Java对象在传入时不会在JavaScript端损失任何类型信息。由于脚本在JVM上原生运行,我们可以在Nashron上使用Java API或外部库的全部功能。

在JavaScript中调用Java方法

在JavaScript中调用Java方法十分容易。我们首先需要定义一个Java静态方法。

static String fun1(String name) {
    System.out.format("Hi there from Java, %s", name);
    return "greetings from java";
}

Java类可以通过Java.typeAPI扩展在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

在使用JavaScript原生类型调用Java方法时,Nashorn 如何处理类型转换?让我们通过简单的例子来弄清楚。

下面的Java方法简单打印了方法参数的实际类型:

static void fun2(Object object) {
    System.out.println(object.getClass());
}

为了理解背后如何处理类型转换,我们使用不同的JavaScript类型来调用这个方法:

MyJavaClass.fun2(123);
// class java.lang.Integer

MyJavaClass.fun2(49.99);
// class java.lang.Double

MyJavaClass.fun2(true);
// class java.lang.Boolean

MyJavaClass.fun2("hi there")
// class java.lang.String

MyJavaClass.fun2(new Number(23));
// class jdk.nashorn.internal.objects.NativeNumber

MyJavaClass.fun2(new Date());
// class jdk.nashorn.internal.objects.NativeDate

MyJavaClass.fun2(new RegExp());
// class jdk.nashorn.internal.objects.NativeRegExp

MyJavaClass.fun2({foo: "bar"});
// class jdk.nashorn.internal.scripts.JO4

JavaScript原始类型转换为合适的Java包装类,而JavaScript原生对象会使用内部的适配器类来表示。要记住jdk.nashorn.internal中的类可能会有所变化,所以不应该在客户端面向这些类来编程。

任何标记为“内部”的东西都可能会从你那里发生改变。

ScriptObjectMirror

在向Java传递原生JavaScript对象时,你可以使用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)));
}

当向这个方法传递对象(哈希表)时,在Java端可以访问其属性:

MyJavaClass.fun3({
    foo: "bar",
    bar: "foo"
});

// Object: [foo, bar]

我们也可以在Java中调用JavaScript的成员函数。让我们首先定义JavaScript Person类型,带有属性firstNamelastName,以及方法getFullName

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.getFullName = function() {
        return this.firstName + " " + this.lastName;
    }
}

JavaScript方法getFullName可以通过callMember()ScriptObjectMirror 上调用。

static void fun4(ScriptObjectMirror person) {
    System.out.println("Full Name is: " + person.callMember("getFullName"));
}

当向Java方法传递新的Person时,我们会在控制台看到预期的结果:

var person1 = new Person("Peter", "Parker");
MyJavaClass.fun4(person1);

// Full Name is: Peter Parker
语言扩展

Nashorn定义了多种对ECMAScript标准的语言和API扩展。让我们看一看最新的特性:

类型数组

JavaScript的原生数组是无类型的。Nashron允许你在JavaScript中使用Java的类型数组:

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]);  // 17

array[0] = "wrong type";
print(array[0]);  // 0

array[0] = "17.3";
print(array[0]);  // 17

int[]数组就像真实的Java整数数组那样。但是此外,在我们试图向数组添加非整数时,Nashron在背后执行了一些隐式的转换。字符串会自动转换为整数,这十分便利。

集合和范围遍历

我们可以使用任何Java集合,而避免使用数组瞎折腾。首先需要通过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

为了迭代集合和数组,Nashron引入了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, bar

for each (var e in map.values()) print(e);  // val1, val2
Lambda表达式和数据流

每个人都热爱lambda和数据流 -- Nashron也一样!虽然ECMAScript 5.1没有Java8 lmbda表达式的简化箭头语法,我们可以在任何接受lambda表达式的地方使用函数字面值。

var list2 = new java.util.ArrayList();
list2.add("ffffd2");
list2.add("aaa2");
list2.add("bbb1");
list2.add("aaa1");
list2.add("bbb3");
list2.add("ccc");
list2.add("bbb2");
list2.add("ffffd1");

list2
    .stream()
    .filter(function(el) {
        return el.startsWith("aaa");
    })
    .sorted()
    .forEach(function(el) {
        print(el);
    });
    // aaa1, aaa2
类的继承

Java类型可以由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);              // 10
System.out["println"](11.0);         // 11.0
System.out["println(double)"](12);   // 12.0

当使用重载参数调用方法时,传递可选参数类型println(double)会指定所调用的具体方法。

Java Beans

你可以简单地使用属性名称来向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);    // bar
o1.foo = "BAM";
print(o2.foo);    // BAM
字符串去空白

我喜欢去掉空白的字符串:

print("   hehe".trimLeft());            // hehe
print("hehe    ".trimRight() + "he");   // hehehe
位置

以防你忘了自己在哪里:

print(__FILE__, __LINE__, __DIR__);
导入作用域

有时一次导入多个Java包会很方便。我们可以使用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.typeJavaImporter直接访问:

var list = new java.util.ArrayList();
list.add("s1");
list.add("s2");
list.add("s3");

下面的代码将Java列表转换为JavaScript原生数组:

var jsArray = Java.from(list);
print(jsArray);                                  // s1,s2,s3
print(Object.prototype.toString.call(jsArray));  // [object Array]

下面的代码执行相反操作:

var javaArray = Java.to([3, 5, 7, 11], "int[]");
访问超类

在JavaScript中访问被覆盖的成员通常比较困难,因为Java的super关键字在ECMAScript中并不存在。幸运的是,Nashron有一套补救措施。

首先我们需要在Java代码中定义超类:

class SuperRunner implements Runnable {
    @Override
    public void run() {
        System.out.println("super run");
    }
}

下面我在JavaScript中覆盖了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()方法。

加载脚本

在JavaScript中加载额外的脚本文件非常方便。我们可以使用load函数加载本地或远程脚本。

我在我的Web前端中大量使用Underscore.js,所以让我们在Nashron中复用它:

load("http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js");

var odds = _.filter([1, 2, 3, 4, 5, 6], function (num) {
    return num % 2 == 1;
});

print(odds);  // 1, 3, 5

外部脚本会在相同JavaScript上下文中被执行,所以我们可以直接访问underscore 的对象。要记住当变量名称互相冲突时,脚本的加载可能会使你的代码崩溃。

这一问题可以通过把脚本文件加载到新的全局上下文来绕过:

loadWithNewGlobal("script.js");
命令行脚本

如果你对编写命令行(shell)脚本感兴趣,来试一试Nake吧。Nake是一个Java 8 Nashron的简化构建工具。你只需要在项目特定的Nakefile中定义任务,之后通过在命令行键入nake -- myTask来执行这些任务。任务编写为JavaScript,并且在Nashron的脚本模式下运行,所以你可以使用你的终端、JDK8 API和任意Java库的全部功能。

对Java开发者来说,编写命令行脚本是前所未有的简单...

到此为止

我希望这个教程对你有所帮助,并且你能够享受Nashron JavaScript引擎之旅。有关Nashron的更多信息,请见这里、这里和这里。使用Nashron编写shell脚本的教程请见这里。

我最近发布了一篇后续文章,关于如何在Nashron中使用Backbone.js模型。如果你想要进一步学习Java8,请阅读我的Java8教程,和我的Java8数据流教程。

这篇Nashron教程中的可运行的源代码托管在Github上。请随意fork我的仓库,或者在Twitter上向我反馈。

请坚持编程!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64961.html

相关文章

  • [] 在 Nashron 中使用 Backbone.js

    摘要:原文译者飞龙协议这个例子展示了如何在的引擎中使用模型。在年三月首次作为的一部分发布,并通过以原生方式在上运行脚本扩展了的功能。将二者放在一起下一个目标是在中,例如在服务器上复用模型。最后,我们在中调用函数。总结在中复用现存的库十分简单。 原文:Using Backbone.js with Nashorn 译者:飞龙 协议:CC BY-NC-SA 4.0 这个例子展示了如何在Java8...

    tabalt 评论0 收藏0
  • [] 在 Nashron 中使用 Backbone.js

    摘要:原文译者飞龙协议这个例子展示了如何在的引擎中使用模型。在年三月首次作为的一部分发布,并通过以原生方式在上运行脚本扩展了的功能。将二者放在一起下一个目标是在中,例如在服务器上复用模型。最后,我们在中调用函数。总结在中复用现存的库十分简单。 原文:Using Backbone.js with Nashorn 译者:飞龙 协议:CC BY-NC-SA 4.0 这个例子展示了如何在Java8...

    gotham 评论0 收藏0
  • Java 8 API 示例:字符串、数值、算术和文件

    摘要:示例字符串数值算术和文件原文译者飞龙协议大量的教程和文章都涉及到中最重要的改变,例如表达式和函数式数据流。不仅仅是字符串,正则表达式模式串也能受益于数据流。 Java 8 API 示例:字符串、数值、算术和文件 原文:Java 8 API by Example: Strings, Numbers, Math and Files 译者:飞龙 协议:CC BY-NC-SA 4.0 ...

    KavenFan 评论0 收藏0
  • java8 之 新的Java工具

    简介 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。 这个教程包含Java开发者经常面对的几类问题: 语言编译器库工具运行时(JVM)新的Java工具 Java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具...

    sourcenode 评论0 收藏0
  • Java使用Nashorn,调用Promise实现服务端渲染

    摘要:未来的主要发布基于。在中调用函数支持从代码中直接调用定义在脚本文件中的函数。内置了方法,提调用函数并返回结果。当向方法传递新的时,我们会在控制台看到预期的结果实战通过使用实现服务端渲染。工具类实例化工具类,通过该类操作对象。 Nashorn JavaScript引擎是Java SE 8 的一部分,并且和其它独立的引擎例如 Google V8(用于Google Chrome和Node.j...

    Hwg 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<