尚学堂 老师好!

上海:15201841284

广州:020-2989 6995

深圳:0755-23061965

武汉:027-8798 9193

西安:029-8822 8155

Java 8 的 Nashorn 脚本引擎教程

本文为了解所有关于 Nashorn JavaScript 引擎易于理解的代码例子。 Nashorn JavaScript 引擎是Java SE 8的一部分,它与其它像Google V8 (它是Google Chrome 和Node.js的引擎)的独立引擎相互竞争。 Nashorn 扩展了Java在JVM上运行动态JavaScript脚本的能力。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

在接下来的大约15分钟里,您将学习如何在 JVM 上动态运行 JavaScript。 通过一些简短的代码示例演示最近 Nashorn 的语言特性。 学习 Java 与 JavaScript 的相互调用。最后包括如何在日常的 Java 业务中整合动态脚本。

Java 8 的 Nashorn 脚本引擎教程

更新- 目前我正在使用JavaScript实现一个针对浏览器类Java 8 流的API。 如果你感兴趣,请下载代码 Stream.js on GitHub。 我将非常感激你的反馈。

使用Nashorn

Nashorn javascript 引擎要么在java程序中以编程的方式使用要么在命令行工具jjs使用,jjs在目录$JAVA_HOME/bin中。如果你准备建立一个jjs的符号链接,如下:

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

本教程关注的是在java代码中使用 nashorn ,所以我们现在跳过jjs。用java代码来一个简单的  HelloWorld示例,如下:

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

为了在java中执行JavaScript代码,首先使用原先Rhino (旧版Java中来自Mozilla的引擎)中的包javax.script来创建一个nashorn脚本引擎。.

既可以向上面那样把JavaScript代码作为一个字符串来直接执行,也可放入一个js脚本文件中,如:

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

Nashorn javascript是基于 ECMAScript 5.1 ,但nashorn后续版本将支持 ECMAScript 6:

当前Nashorn的策略是遵循ECMAScript规范。 当我们发布JDK 8时,我们将实现ECMAScript 5.1标准。后续的 Nashorn的版本将实现 ECMAScript Edition 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)); }; 

为了调用函数,你首先得把脚本引擎转换为 Invocable。NashornScriptEngine 实现了 Invocable 接口且定义一个调用JavaScript函数的方法 invokeFunction ,传入函数名即可。

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

上述代码的执行将在控制台打印三行信息。调用 print 函数将输出内容通过管道送到 System.out 控制台,因此我们首先看到的是 JavaScript打印的信息。

现在我们通过传递任意的 Java 对象去调用第二个函数:

 invocable.invokeFunction("fun2", new Date());   invocable.invokeFunction("fun2", LocalDateTime.now());   invocable.invokeFunction("fun2", new Person());  

你可以传递任意 Java 对象而不会在 JavaScript 这边丢失类型信息。因为脚本本身是在 JVM 虚拟机中执行的,我们可以完全利用 nashorn 引擎的 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"; } 

JavaScript 可通过 Java.type API 来引用 Java 类。这跟在 Java 类中引入其他类是类似的。当定义了 Java 类型后我们可直接调用其静态方法 fun1() 并打印结果到 sout。因为方法是静态的,所以我们无需创建类实例。

 var MyJavaClass = Java.type('my.package.MyJavaClass');  var result = MyJavaClass.fun1('John Doe'); print(result);    

当调用java 方法时,Nashorn怎样处理原生JavaScript类型与java类型转换?让我们用一个简单的例子来发现。

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

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

为了解引擎如何处理类型转换,我使用不同JavaScript类型来调用java方法:

 MyJavaClass.fun2(123);   MyJavaClass.fun2(49.99);   MyJavaClass.fun2(true);   MyJavaClass.fun2("hi there")   MyJavaClass.fun2(new Number(23));   MyJavaClass.fun2(new Date());   MyJavaClass.fun2(new RegExp());   MyJavaClass.fun2({foo: 'bar'});  

下一个示例更改参数类型Object为ScriptObjectMirror,因此我们能获取到传入JavaScript中对象的一些信息:

 static void fun3(ScriptObjectMirror mirror) {     System.out.println(mirror.getClassName() + ": " +         Arrays.toString(mirror.getOwnKeys(true))); } 

当我们把传递对象hash到方法中,在Java端就能访问这些属性:

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

我们也可以在Java端调用JavaScript对象中的函数。我们首先定义一个JavaScript类型 Person,包含属性 firstName 、lastName 和函数getFullName。

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

javascript 函数getFullName 能被 ScriptObjectMirror 的callMember()调用。

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

当我们传入一个新的person给java 方法时,我们能在控制台看到预期结果:

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

语言扩展

Nashorn 定义一系列的语言和扩展了 ECMAScript 标准的API。 让我们直接进入最新的功能:

类型数组

原始javascript 数组时无类型的。 Nashorn 运行你在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[0] = "17"; print(array[0]);    array[0] = "wrong type"; print(array[0]);    array[0] = "17.3"; print(array[0]);   

int[] 数组的行为像一个真正的 java int 数组。 但当我们试图添加非整数的值的数组时,Nashorn 会执行隐式类型转换。 字符串会自动转换为int,这相当方便。

集合与For Each

我们可以使用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);   

为了遍历集合和数组中的元素,Nashorn 引入了 for each 语句。这就像是 Java 的 for 循环一样。

这里是一个对集合元素进行遍历的例子,使用的是 :

 var map = new java.util.HashMap(); map.put('foo', 'val1'); map.put('bar', 'val2');  for each (var e in map.keySet()) print(e);    for each (var e in map.values()) print(e);   

Lambda 表达式和 Streams

似乎大家都比较喜欢 Lambda 和 Streams —— Nashorn 也是!虽然 ECMAScript 5.1 中缺少 Java 8 Lambda 表达式中的紧缩箭头的语法,但我们可以在接受 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);     });      

扩展类

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

参数重载

方法和函数可以使用点符号或方括号来进行调用。

 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

我们不需要常规的用 getter 或者 setter 来访问类成员属性,可直接用属性名简单访问 Java Bean 中的属性。例如:

 var Date = Java.type('java.util.Date'); var date = new Date(); date.year += 1900; print(date.year);   

函数语法

如果只是简单的一行函数我们可以不用大括号:

 function sqr(x) x * x; print(sqr(3));    // 9 

属性绑定

来自不同对象的属性可以绑定在一起:

 var o1 = {}; var o2 = { foo: 'bar'};  Object.bindProperties(o1, o2);  print(o1.foo);     o1.foo = 'BAM'; print(o2.foo);     

字符串处理

我喜欢字符串裁剪.

 print("   hehe".trimLeft()) print("hehe    ".trimRight() + "he") 

在哪里

以防忘记你在哪里:

 print(__FILE__, __LINE__, __DIR__); 

Import 的范围

有时,这在一次性导入多个java 包时非常有用。我们可以使用JavaImporter并结合with,在with块范围内引用:

 var imports = new JavaImporter(java.io, java.lang); with (imports) {     var file = new File(__FILE__);     System.out.println(file.getAbsolutePath());      } 

数组转换

有些包时可以直接使用而不必利用 Java.type 或JavaImporter引入,如 java.util:

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

如下的代码演示了将java list转换为JavaScript的数组:

 var jsArray = Java.from(list); print(jsArray);                                   print(Object.prototype.toString.call(jsArray));   

其他的方式:

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

调用父类函数

在 JavaScript 中访问重载的成员会有一点点尴尬,因为 ECMAScript 没有类似 Java 的 super 关键字一样的东西。所幸的是 Nashorn 有办法解决。

首先我们在 Java 代码中定义一个超类:

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

接下来我们在 JavaScript 中重载 SuperRunner 。创建一个新的 Runner 实例时请注意 Nashorn 的扩展语法:其重载成员的语法是参考 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();    

我们使用Java.super调用了重载方法 SuperRunner.run()。

在JavaScript中执行其它脚本是十分容易的。我们可以load函数载入本地或远程的脚本。

在我的很多web前端中都使用了 Underscore.js ,因此在Nashorn中我们可以重用 Underscore:

 load('https://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);   

扩展脚本的执行是在同一个 JavaScript 上下文中,因此我们可以直接访问 underscore 变量。记住脚本的加载可能会因为变量名的重叠导致代码出问题。

我们可以通过将加载的脚本文件放置到一个新的全局上下文来解决这个问题:

 loadWithNewGlobal('script.js') 

命令行脚本

如果你对用 Java 编写命令行脚本很感兴趣的话,可以试试 Nake 。Nake 是一个为 Java 8 Nashorn 准备的简单 Make 工具。你可以在 Nakefile 文件中定义任务,然后使用 nake -- myTask 来运行任务。任务使用 JavaScript 编写并通过 Nashorn 脚本模式运行,因此你可以让你的终端应用完全利用 Java 8 API 和其他 Java 库强大的功能。

对 Java 开发者而言,编写命令行脚本从来没有如此简单过。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

  • 北京校区
  • 西安校区
  • 山西校区
  • 武汉校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办
  • 黑龙江项目办

北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE+微服务+大数据     大数据+机器学习+平台架构     Python+数据分析+机器学习  人工智能+模式识别+强化学习   WEB前端+移动端+服务端渲染

地址:陕西省西安市高新区西安软件园西区创新信息大厦A座三层尚学堂

电话:029-88228155 / 18291433445

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

武汉学区地址:武汉市东湖高新区光谷金融港B22栋11楼
咨询电话:027-87989193

网址:http://www.cssxt.com/
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城大道U8智造产业园U6栋3楼
咨询电话:0755-23061965 / 18898413781

上海尚学堂校区地址:上海市浦东新区城丰路650号
咨询电话:021-67690939

广州校区地址:广州市天河区车陂街道大岗路5号中侨广场2栋321室(四号线车陂站D出口,或brt车陂站)
咨询电话:18948349646

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

黑龙江项目办
地点:哈尔滨市松北区博文路青年部落孵化器1层
电话:15321415678
Copyright 2006-2021 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
网站维护:北京尚学堂科技有限公司昌平分公司