Java 8 Nashorn 教程

澳门新葡亰网站注册 1

我们无法在一篇博文里解释 JavaScript 的所有细节。如果你正或多或少地涉及了
web 应用程序开发,那么,我们的 Java
工具和技术范围报告揭示了,大多数(71%)Java
开发者被归到了这一类,只是你对 JavaScript 遇到了阻碍。

原文:Java 8 Nashorn Tutorial

译者:飞龙

澳门新葡亰网站注册,协议:CC BY-NC-SA 4.0

毫无疑问,你已经知道了 Java 和
JavaScript,不管它们有着多么类似的命名,彼此没有共享太多共通之处。Java
的静态类型、符合直接规律的简单语法和冗长,与 JavaScript
的动态、缺乏一致性原则和怪异,有着巨大的不同。

这个教程中,你会通过简单易懂的代码示例,来了解Nashorn
JavaScript引擎。Nashorn JavaScript引擎是Java SE 8
的一部分,并且和其它独立的引擎例如Google V8(用于Google
Chrome和Node.js的引擎)互相竞争。Nashorn通过在JVM上,以原生方式运行动态的JavaScript代码来扩展Java的功能。

然而,JavaScript 是 web 的编程语言,最近由于 Node.js 和 JVM 自己的
Nashorn JavaScript 引擎的发展,在服务器端获得了相当的注意。

在接下来的15分钟内,你会学到如何在JVM上在运行时动态执行JavaScript。我会使用小段代码示例来演示最新的Nashron语言特性。你会学到如何在Java代码中调用JavaScript函数,或者相反。最后你会准备好将动态脚本集成到你的Java日常业务中。

本文,我不想只是漫谈 JavaScript
的好与不好,或重复任何人都能免费找到的、不计其数的 JavaScript
教程。我想列出一些有助于理解 JavaScript
做为一种语言的技术点,并从接近 horse 的角度来理解。

澳门新葡亰网站注册 1

我们将在本文包含下列语言级别的技术点:

更新 –
我现在正在编写用于浏览器的Java8数据流API的JavaScript实现。如果你对此感兴趣,请在Github上访问Stream.js。非常期待你的反馈。

  • JavaScript 的通用性
  • JavaScript 的函数编程问题
  • 不同于 Java 的继承

Nashorn
JavaScript引擎可以在Java代码中编程调用,也可以通过命令行工具jjs使用,它在$JAVA_HOME/bin中。如果打算使用jjs,你可能希望设置符号链接来简化访问:

另外,你会找到一些工具方面的推荐,没有这些工具,你是不想着手 JavaScript
项目的,包含了构建系统的代码质量分析和测试框架方面的工具。

$ cd /usr/bin$ ln -s $JAVA_HOME/bin/jjs jjs$ jjsjjs> print('Hello World');

优点

这个教程专注于在Java代码中调用Nashron,所以让我们先跳过jjs。Java代码中简单的HelloWorld如下所示:

编写一次,差不多处处运行!

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");engine.eval("print('Hello World!');");

毋庸置疑 JavaScript 是 web
编程语言,是很多其它语言的编译目标,也是用来证明有时候人们只是想拥有更多自由时间的终极方式。尽管如此,这不是一件坏事。每一台能够浏览现代网站的电脑都装备了具有性能和可用的
JavaScript 引擎。最重要的是,JavaScript 代码可以在后端运行。

为了在Java中执行JavaScript,你首先要通过javax.script包创建脚本引擎。这个包已经在Rhino(来源于Mozilla、Java中的遗留JS引擎)中使用了。

内置到我们喜爱的 JVM 的、轻量级高性能 JavaScript 运行时
Nashorn,完全能够解释 JavaScript 脚本,还能够解释项目中带有 Java 代码的
JavaScript 脚本。

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

鉴于每台电脑运行时都可获得的自由,JavaScript 成为 Java 体验的完美延续。

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

函数式编程:一等公民是函数,而不是递归

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

JavaScript
中的函数是第一类公民,它们是值,可被存储在变量里、传递给其它函数、在适当的时候再执行。

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

这打开了函数式编程世界的大门,这是结构化 JavaScript 编程的完美方式。

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

注意,JavaScript
里的对象是任何东西的映射,对象的每个特性(attribute)都在同一个映射里:函数、属性(property)、构造器;易变性带来了更大的隐患,而对于
Java,你至少能够确保方法和字段结构在某种程度上是稳定的。

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

反过来,这使得函数式编程更加有利:涉及到小的、可理解函数和不变的数据结构是在
JavaScript 里运行的方式。

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

这不是没有依据的,下面是在 JavaScript 里定义一个 reduce
函数的例子,来自于《Eloquent JavaScript》一书。

var fun1 = function { print('Hi there from Javascript, ' + name); return "greetings from javascript";};var fun2 = function  { print("JS Class Definition: " + Object.prototype.toString.call;};
function forEach (array, action) {
for (var i = 0; i < array.length; i++) {
action (array[i]); //apply action to every element of the arra. }
}

function reduce (combine, base, array) {
forEach (array, function (element) {
base = combine (base, element); // and here we apply function passed as ‘combine’ parameter to ‘base’ and ‘element’ });
return base;
}

function add (a, b) { // btw, this is how you define a function in JavaScript return a + b;
}

function sum (numbers) {
return reduce (add, 0, numbers);
}

为了调用函数,你首先需要将脚本引擎转换为InvocableInvocable接口由NashornScriptEngine实现,并且定义了invokeFunction方法来调用指定名称的JavaScript函数。

注意:我们没有在这里使用 reduce 的递归版本。JavaScript
没有以尾调用【注1】为特色,这意味着每个函数的递归版本都将用到栈的深度,和
Java 一样,如果你递归太深,程序就崩溃。

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;System.out.println(result.getClass;// Hi there from Javascript, Peter Parker// greetings from javascript// class java.lang.String

继承:就像真实的世界

执行这段代码会在控制台产生三行结果。调用函数print将结果输出到System.out,所以我们会首先看到JavaScript输出。

JavaScript
的继承是基于原型的。即,你没有扩展了其它类型的类型,而实际上,你拥有的实例从其它实例继承了功能。

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

想象一下,对象A就像一个映射,我们刚才稍微提到了一些、但是用了不同的视角,然后另一个类似映射的对象B从A继承了一切。

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]

这说明B可以访问A所有部分:A的方法、字段等等。

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

在实践中,我从来没有看到有人实际使用简单的基于原型的继承。通常当某人需要继承时,他只是构造类,因此你可以用到所有广泛的技能,和基于类的继承的工作模式。

——Rene Saarsoo,XRebel 前端工程师

在JavaScript中调用Java方法十分容易。我们首先需要定义一个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打印信息。由于方法是静态的,我们不需要首先创建实例。

列出不可靠的 JavaScript 设计上的决定比想象中要容易。在 JavaScript
程序中要避免的最明显的地方就是全局变量的声明

var MyJavaClass = Java.type('my.package.MyJavaClass');var result = MyJavaClass.fun1('John Doe');print;// Hi there from Java, John Doe// greetings from java

注意,在 JavaScript 里,无论什么时候,不使用 var
关键词定义变量,那么定义的变量被推到了它们被定义的作用域顶端。这意味着,每个用这种方式定义的变量将跑到全局范围顶部,这会引发冲突以及你和同事不可预期的头痛。

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

可以开启 strict 模式。只需在脚本文件顶部写上“use
strict”,那么不经意编写的全局变量声明将显示错误。

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

JavaScript 与 Java
另一个重要的不同点在于,前者是动态类型语言,其真谛是所有东西都可以是任何类型。这很明显了,实在不能再强调了:不要针对不同类型的值,去复用相同的变量

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