语言扩展

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("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类型可以由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');