【JDK8】Nashorn 與 Java API(二)


Nashorn 與 Java API(一) 中介紹了一些如何撰寫 JavaScript 來使用 Java API,語言本身並不是一對一,因而若要進一步使用 Java 的一些特性,就得有一些額外的對應方式。

指定重載方法

實際上方法在 JavaScript 不過是個物件上的特性,為函式實例,因此,除了使用 . 來存在方法之外,也可以使用 [] 來取得代表方法的函式:

var System = Java.type('java.lang.System');
System.out.println('Hello, World');    // Hello, World
System.out['println']('Hello, World'); // Hello, World

Java 中支援重載(Overload)方法,例如,System.outprintln 有多個重載版本,如果你想指定呼叫特定的重載版本,可以使用 [] 時指定參數列。例如:

var System = Java.type('java.lang.System');
System.out['println'](3.14);          // 3.14
System.out['println(double)'](3.14);  // 3.14
System.out['println(int)'](3.14);     // 3

實例方法也可以使用這種方式:

var PrintStream = Java.type('java.io.PrintStream');
var file = new PrintStream('test.txt');
file['println(int)'](3.14);  // test.txt 中只存 3 這個數值
file.close();

JavaImporter、importPackage、 importClass

Java 中有 import 語法,在 Nashorn 中可以用 Java.type 來模擬 import 特定類別的作用:

var PrintStream = Java.type('java.io.PrintStream');

如果想同時將數個類別或套件放在某個名稱空間下,可以使用 JavaImport。例如:

var commons = new JavaImporter(java.util, java.lang);
with(commons) {
    var lt = Arrays.asList(1, 2, 3);
    System.out.println(lt);   // [1, 2, 3]
}

Nashorn 內建了 mozilla_compat.js,可以使用 load 載入後,使用 imoprtPackageimportClass,前者相當於在 Java 中 import 時在類別的部份使用 *,後者用來 import 某個類別。例如:

load('nashorn:mozilla_compat.js');

importClass(java.lang.System);
importPackage(java.util);

System.out.println('Hello, World'); // Hello, World

var lt = Arrays.asList(1, 2, 3);
System.out.println(lt);             // [1, 2, 3]

實作介面

如果想要實作介面,可以使用 Java.extend。例如:

load('nashorn:mozilla_compat.js');

importPackage(java.lang);

var RunDemo = Java.extend(Runnable, {
    run: function() {
        [1, 2, 3].forEach(print);
    }
});

var th = new Thread(new RunDemo());
th.start();
th.join();

不使用 Java.extend,也可以用類似 Java 的匿名類別實作語法:

load('nashorn:mozilla_compat.js');

importPackage(java.lang);

var r = new Runnable {
    run: function() {
        [1, 2, 3].forEach(print);
    }
};

var th = new Thread(r);
th.start();
th.join();

如果方法的參數型態是個只具備單一抽象方法的介面,可以直接使用函式實作。例如:

var IntStream = Java.type("java.util.stream.IntStream");
var sum = IntStream.of(1, 2, 3, 4, 5, 6)
                   .filter(function(elem) {
                       return elem > 2;
                   })
                   .sum();
print(sum); // 18

繼承類別

如果是要繼承抽象類別並實作抽象方法,方式與實作介面是類似的:

load('nashorn:mozilla_compat.js');

importPackage(java.util);

var r = new TimerTask {
    run: function() {
        [1, 2, 3].forEach(print);
    }
};

r.run();

var DemoTask = Java.extend(TimerTask, {
    run: function() {
        [1, 2, 3].forEach(print);
    }
});

new DemoTask().run();

然而,如果是繼承非抽象類別,就必須使用 java.extend

load('nashorn:mozilla_compat.js');

importPackage(java.lang);

var DemoThread1 = Java.extend(Thread, {
    run: function() {
        [1, 2, 3].forEach(print);
    }
});

new DemoThread1().run();

var DemoThread2 = Java.extend(Thread);

(new DemoThread2 {
    run: function() {
        [1, 2, 3].forEach(print);
    }
}).run();

實際上,Java.extend 每次呼叫後,都會建立一個子類別的型態物件,因此,以下兩種寫法有些不同:

load('nashorn:mozilla_compat.js');

importPackage(java.lang);

var DemoThread1 = Java.extend(Thread, {
    run: function() {
        [1, 2, 3].forEach(print);
    }
});

var DemoThread2 = Java.extend(Thread, {
    run: function() {
        [1, 2, 3].forEach(print);
    }
});

System.out.println(new DemoThread1().getClass() === new DemoThread2().getClass()); // false

var DemoThread = Java.extend(Thread);

var th1 = new DemoThread {
    run: function() {
        [1, 2, 3].forEach(print);
    }
};

var th2 = new DemoThread {
    run: function() {
        [1, 2, 3].forEach(print);
    }
};

System.out.println(th1.getClass() === th2.getClass());  // true

呼叫父類別方法

如果想呼叫父類別建構式並同時重新實作某方法,可以如下:

load('nashorn:mozilla_compat.js');

importPackage(java.lang);

var Th = Java.extend(Thread);

var r = function() { 
    [1, 2, 3].forEach(function(elem) {
        print(elem);
    });
};

var th1 = new Th(r) {
    run : function() {
        Java.super(th1).run();
        [4, 5, 6].forEach(function(elem) {
            print(elem);
        });
    }
};

th1.start();
th1.join();

上面的範例也示範了 Java.super 的使用,它可用來呼叫父類別的方法。

從 Java 中執行 JavaScript

要在 Java 中使用 Nashorn 執行 JavaScript 的話,可以如下取得 Nashorn 引擎:

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("nashorn");
engine.eval("print('Hello, World!')");
engine.eval(new FileReader("C:\\workspace\\hello.js"));

這是 JSR 223: Scripting for the Java Platform 規範中的東西,你可以在 Oracle 的 Scripting for the Java Platform 找到相關資訊。

實際上,這兩篇〈Nashorn 與 Java API〉的內容,在 Oracle Java Platform, Standard Edition Java Scripting Programmer's Guide3 Using Java From Scripts 就有談到,如果想要瞭解更多如何在 Java 中呼叫 JavaScript,可以進一步參考 2 The Java Scripting API

專門說明 Nashorn 的 Oracle 文件,則可以在 Java Platform, Standard Edition Nashorn User's Guide 找到。