【JDK8】Nashorn 與 Java API(一)


Nashorn 是基於 JVM 的 JavaScript 引擎,自然地,與 JVM 資源的互通性會是它的重點之一,基本上,你可以使用 JavaScript 的語法與 Nashorn 的擴充語法,存取 Java 的相關 API。

取得 Java API

如果想取得 Java 標準類別,可以直接指定套件階層來存取。例如:

jjs> java.lang
[JavaPackage java.lang]
jjs> java.lang.System
[JavaClass java.lang.System]

如果是自定義類別,可以使用 Packages 物件,假設 CLASSPATH 中(可在執行 jjs 時使用 -cp 指定)可以存取到 cc.openhome.GetStarted,那麼可以如下取得 Java API:

jjs> Packages.cc.openhome
[JavaPackage cc.openhome]
jjs> Packages.cc.openhome.GetStarted
[JavaClass cc.openhome.GetStarted]

不過以上兩種方式都有些缺點,使用 . 來作特性存取需要成本,會有效率問題,如果你提供了錯誤的類別名稱,Nashorn 會假設你指定了套件,即使實際上不存在該套件:

jjs> java.lang.Syz
[JavaPackage java.lang.Syz]

為了避免這些問題,Nashorn 提供 Java.type,你可以指定完整名稱(Fully quailfied name),以取得代表 Java 類別的物件:

jjs> var System = Java.type('java.lang.System')
jjs> System
[JavaClass java.lang.System]

使用 Java.type 的好處是,你可以直接取得陣列的代表類別:

jjs> Java.type('int[]')
[JavaClass [I]
jjs> Java.type('double[]')
[JavaClass [D]
jjs> Java.type('java.lang.String[]')
[JavaClass [Ljava.lang.String;]

JavaClass 在 Nashorn 實際上是一個 function,因此取得之後,就可以用 JavaScript 的方式來操作:

jjs> var ArrayList = Java.type('java.util.ArrayList')
jjs> typeof ArrayList
function
jjs> var lt = new ArrayList()
jjs> lt.add('Justin')
true
jjs> lt.add('Monica')
true
jjs> lt.toString()
[Justin, Monica]

上頭示範了呼叫實例方法的方式,如果是 Java 的靜態方法,在 JavaScript 中就是函式上的特性:

jjs> var System = Java.type('java.lang.System')
jjs> System.currentTimeMillis()
1401330165615
jjs> System.out.println('Hello, World')
Hello, World
null

如果是存取類別中的靜態類別,可以依上述的方式彈性存取,例如:

jjs> Java.type('java.util.Map.Entry')
[JavaClass java.util.Map$Entry]
jjs> Java.type('java.util.Map$Entry')
[JavaClass java.util.Map$Entry]
jjs> Java.type('java.util.Map').Entry
[JavaClass java.util.Map$Entry]

JavaScript 基本型態與 Java API

JavaScript 的基本型態有數值、字串與布林值,因為 Nashorn 基於 JVM,因此這些型態實際上會與 Java API 有所對應,先來看實字的對應:

jjs> (1).class
class java.lang.Integer
jjs> (11111111111).class
class java.lang.Long
jjs> (1111111111111111111111).class
class java.lang.Double
jjs> (3.14).class
class java.lang.Double
jjs> 'Justin'.class
class java.lang.String
jjs> true.class
class java.lang.Boolean

可以看到,整數實字依長度不同,會分別對應至 IntegerLongDouble,然而,如果你使用 JavaScript 的 Number 函式建立的數字,都是 Double

jjs> Number(1).class
class java.lang.Double

JavaScript 與 Java 陣列

JavaScript 中使用陣列實字建立的物件,依舊是 Array 實例,操作的特性依舊就是 JavaScript 規範中的特性:

jjs> var arr = [1, 2, 3]
jjs> arr.length
3
jjs> arr[3] = 4
4
jjs> arr
1,2,3,4
jjs> arr.constructor
function Array() { [native code] }

如果想要建立 Java 中的陣列物件,可以如下:

jjs> var IntArray = Java.type('int[]');
jjs> var arr = new IntArray(3)
jjs> arr[0] = 1
1
jjs> arr[1] = 2
2
jjs> arr[2] = 3
3
jjs> arr[3] = 4
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3

上頭的陣列是具備型態約束的,也就是只能裝整數,試著在其中放些字串或浮點數值,猜猜你會看到什麼?

如果想將 JavaScript 陣列轉為 Java 陣列,可以使用 Java.to

jjs> var arr = Java.to([1, 2, 3], Java.type('int[]'))
jjs> arr[0]
1
jjs> arr[1]
2
jjs> arr[2]
3
jjs> arr[3]
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3

Java.to([1, 2, 3], Java.type('int[]')) 可以簡單寫為 Java.to([1, 2, 3], 'int[]'),如果想將 List 轉為 JavaScript 陣列,則使用 Java.from

jjs> var lt = new java.util.ArrayList();
jjs> var arr = Java.from(lt)
jjs> Object.prototype.toString.call(arr)
[object Array]

for each 語法

如果你使用 JavaScript 的 for 語法來如下存取陣列,會取得的是陣列的索引:

jjs> for(var i in ['a', 'b', 'c']) print(i)
0
1
2

Nashorn 擴充了一個 for each 語法,可以讓你直接取得陣列元素值:

jjs> for each (var elem in ['a', 'b', 'c']) print(elem)
a
b
c

這個 for each 語法,其實也可以用 JavaScript 物件、Java 陣列、IterableMap 實例上。例如:

jjs> var map = new java.util.HashMap()
jjs> map.put('k1', 10)
null
jjs> map.put('k2', 20)
null
jjs> for each(var value in map) print(value)
10
20
jjs> for each(var value in {x: 10, y: 20}) print(value)
10
20
jjs>

JavaBean、List 與 Map

JavaBean 物件的 Getter、Setter,可以在 Nashorn 中使用 .[] 存取。例如:

jjs> var Date = Java.type('java.util.Date')
jjs> var instant = new Date()
jjs> instant.time
1401334149733
jjs> instant['time']
1401334149733
jjs> instant.time = 1401334149733 + 1000
1401334150733
jjs> instant.time
1401334150733

List 實例在 Nashorn 中,可以使用 [] 指定索引來代替 get。例如:

jjs> var ArrayList = java.util.ArrayList
jjs> var lt = new ArrayList()
jjs> lt.add(1)
true
jjs> lt.add(2)
true
jjs> lt[0]
1
jjs> lt[1]
2

Map 實例在 Nashorn 中,也可以使用 .[] 存取:

jjs> var HashMap = java.util.HashMap
jjs> var map = new HashMap
jjs> map['k1'] = 10
10
jjs> map.k2 = 20
20
jjs> map.get('k1')
10
jjs> map['k2']
20