この章では、Nashornエンジンで解釈されるスクリプトからJavaにアクセスする方法について説明します。
この章の各項には、Nashornエンジンの対話型言語シェル・モードで解釈されるスクリプト文の例が含まれています。この対話型シェルは、スクリプトを指定せずにjjs
コマンドを実行すると起動されます。これは何かを試す場合に便利ですが、Nashorn Java APIの主な目的は、JavaアプリケーションをNashornエンジンで解釈できるスクリプトとして作成することです。
Nashornを使用してパッケージおよびクラスにアクセスするには、2つの方法があります。従来の方法ではPackages
グローバル・オブジェクトを使用しますが、推奨される方法ではJava
グローバル・オブジェクトを使用します。この項では、両方の方法について説明します。
事前に定義された最上位のPackages
オブジェクトでは、完全修飾名をPackages
オブジェクトのプロパティと同じように使用して、Javaパッケージおよびクラスにアクセスできます。次の例は、MyPackage.jar
がクラス・パスに含まれている場合に、MyPackage
パッケージとそのMyClass
クラスにアクセスする方法を示しています。
jjs> Packages.MyPackage [JavaPackage MyPackage] jjs> Packages.MyPackage.MyClass [JavaClass MyPackage.MyClass]
Javaパッケージおよびクラスへのアクセスは、カスタム・パッケージおよびクラスへのアクセスより簡単です。参考までに、標準Javaパッケージ(com
、edu
、java
、javafx
、javax
およびorg
)には、それぞれグローバル・オブジェクトが定義されています。これらは、Packages
オブジェクトのプロパティに対応する別名を持っています。次の例は、java.lang
パッケージとjava.lang.System
クラスにアクセスする方法を示しています。
jjs> java.lang [JavaPackage java.lang] jjs> typeof java.lang object jjs> java.lang.System [JavaClass java.lang.System] jjs> typeof java.lang.System function
前の例からわかるように、NashornはJavaパッケージをJavaPackage
オブジェクトとして解釈し、Javaクラスを(そのクラスのコンストラクタとして使用できる) JavaClass
関数オブジェクトとして解釈します。クラスのインスタンス化の詳細は、Javaオブジェクトの作成を参照してください。
従来の方法によるJavaパッケージおよびクラスへのアクセスは、直感的で簡単ですが、同時に次の理由で不十分かつ限定的であり、エラーになりやすい傾向があります。
各プロパティのアクセスにコストがかかるため、深い階層のパッケージやクラスへのアクセスが遅くなる可能性があります。
Java配列を作成するための特別な構文がありません。回避策として、java.lang.reflect.Array
クラスを使用する必要があります。
クラス名のスペルを間違えると、Nashornはパッケージ名が指定されたと見なし、それをJavaClass
関数オブジェクトではなくJavaPackage
オブジェクトとして解釈します。それをクラスとして使用しようとしたときにエラーがスローされるまで、このことに気付かない可能性があります。これを回避するには、typeof
演算子を使用して、アクセスしようとしている構造が関数オブジェクトとして解釈される条件をテストします。次の例は、この条件チェックの動作を示しています。
jjs> typeof java.lang.System == "function" true jjs> typeof java.lang.Zyztem == "function" false
前述の方法の短所を回避するため、NashornにはJavaクラスを操作する関数を持つJava
グローバル・オブジェクトが定義されています。Java.type()
関数は、Javaクラスの完全修飾名を含む文字列を取り、対応するJavaClass
関数オブジェクトを返します。次の例は、java.lang.System
クラスにアクセスする方法を示しています。
jjs> Java.type("java.lang.System") [JavaClass java.lang.System]
Javaにクラスをインポートする場合と同じように、スクリプトの先頭でJavaClass
型の変数を宣言することをお薦めします。次の例は、System
変数を宣言し、それにjava.lang.System
クラスの値を代入する方法を示しています。
jjs> var System = Java.type("java.lang.System") jjs> System [JavaClass java.lang.System]
クラスをインスタンス化するには、JavaClass
関数オブジェクトをnew
演算子に渡します。Nashornは、関数に渡された引数に基づいて、対応するコンストラクタを呼び出します。次の例は、デフォルトの初期容量を使用した場合と初期容量を100に設定した場合の、java.util.HashMap
クラスをインスタンス化する方法を示しています。
jjs> var HashMap = Java.type("java.util.HashMap") jjs> var mapDef = new HashMap() jjs> var map100 = new HashMap(100)
次のように、標準ドット表記を使用してstaticフィールド、メソッドおよび内部クラスにアクセスできます。
jjs> Java.type("java.lang.Math").PI 3.141592653589793 jjs> Java.type("java.lang.System").currentTimeMillis() 1375813353330 jjs> Java.type("java.util.Map").Entry [JavaClass java.util.Map$Entry]
内部クラスには、ドル記号($
)または(Javaと一貫性がある)ドットを区切り文字とする内部表現を使用してアクセスすることもできます。
jjs> Java.type("java.util.Map$Entry") [JavaClass java.util.Map$Entry] jjs> Java.type("java.util.Map.Entry") [JavaClass java.util.Map$Entry]
オブジェクトのインスタンス・メソッドを呼び出したり、インスタンス・フィールドにアクセスしたりするには、Javaの場合と同じようにドット演算子を使用します。次の例は、String
オブジェクトのtoUpperCase()
メソッドを呼び出す方法を示しています。
jjs> var String = Java.type("java.lang.String") jjs> var str = new String("Hello") jjs> str Hello jjs> var upper = str.toUpperCase() jjs> upper HELLO
Nashornは、角カッコを使ったメンバー・アクセスもサポートします。その場合は、メンバーの名前を角カッコ([]
)で囲まれた文字列として指定し、その直後にクラス(staticメンバーの場合)またはオブジェクト(インスタンス・メンバーの場合)を指定します。この方法は、ECMAScriptでドット表記の代替として定義されており、Java開発者にとって直感的ではありません。しかし、これを使用してメソッドのオーバーロードのあいまいさを解決できます。デフォルトでは、Nashornは引数にもっとも一致するオーバーロード・メソッドを使用しますが、これが期待する動作であるとはかぎりません。たとえば、次の例に示すように、double
値を出力する場合は、java.lang.System.out.println(double)
メソッド・オーバーロードを使用する必要があります。
jjs> Java.type("java.lang.System").out.println(10) 10 jjs> Java.type("java.lang.System").out["println(double)"](10) 10.0
Nashornでは、JavaBeansのアクセサおよびミューテータ・メソッドを同等のJavaScriptプロパティとして扱うことができます。プロパティの名前は、JavaBeanメソッドの名前から接尾辞のget
またはset
を除き、先頭を小文字にしたものです。たとえば、java.util.Date
オブジェクトのgetYear()
およびsetYear()
メソッドを呼び出すには、次のようにyear
プロパティを使用します。
jjs> var Date = Java.type("java.util.Date") jjs> var date = new Date() jjs> date.year + 1900 2013 jjs> date.year = 2014 - 1900 114 jjs> date.year + 1900 2014
Java配列クラスにアクセスするには、配列とそれに続く角カッコのペアからなる(Java構文と同様の)オブジェクトの型をJava.type()
関数に渡します。次の例は、整数のJava配列とString
オブジェクトのJava配列にアクセスする方法を示しています。
jjs> Java.type("int[]") [JavaClass [I] jjs> Java.type("java.lang.String[]") [JavaClass [Ljava.lang.String;]
配列型オブジェクトを作成した後は、それを使用して他のクラスと同じように配列をインスタンス化できます。次の例に示すように、インデックスを使って配列のエントリにアクセスしたり、ドット表記または(Java構文と同じように)角カッコ表記を使ってメンバーにアクセスしたりできます。
jjs> var IntArrayType = Java.type("int[]") jjs> var arr = new IntArrayType(10) jjs> arr[1] = 123 123 jjs> arr[2] = 321 321 jjs> arr[1] + arr[2] 444 jjs> arr[10] java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10 jjs> arr.length 10
既存のJavaScript配列がある場合は、Java.to()
関数を使用してそれをJava配列に変換できます。次の例は、文字列"a"
、"b"
および"c"
のJavaScript配列を、同じ値を持つjava.lang.String[]
配列に変換する方法を示しています。
jjs> var jsArr = ["a","b","c"] jjs> var strArrType = Java.type("java.lang.String[]") jjs> var javaArr = Java.to(jsArr, strArrType) jjs> javaArr.class class [Ljava.lang.String; jjs> javaArr[0] a
次のように、for
およびfor each
文を使用して、Java配列のインデックスと値を反復処理できます。
jjs> for (var i in javaArr) print(i) 0 1 2 jjs> for each (var i in javaArr) print(i) a b c
Nashornでは、文字列はjava.lang.String
オブジェクトとして解釈されます。ただし、2つの文字列を連結する場合は、jdk.nashorn.internal.runtime.ConsString
クラスのインスタンスを取得します。どちらのクラスもjava.lang.CharSequence
インタフェースを実装しているため、ほとんどのスクリプトではこれで問題ありませんが、場合によっては、メソッドがjava.lang.Object
引数を期待しているときにNashornがConsString
オブジェクトを渡すことがあります。これを避けるには、次の例に示すように、String()
関数を使用して文字列が確実にJava String
オブジェクトになるようにします。
jjs> var a = "abc" jjs> a.class class java.lang.String jjs> var b = a + "def" jjs> b.class class jdk.nashorn.internal.runtime.ConsString jjs> var c = String(b) jjs> c.class class java.lang.String
Nashornは、数値を、実行される計算に応じてjava.lang.Double
、java.lang.Long
またはjava.lang.Integer
オブジェクトとして解釈します。次の例に示すように、Number()
関数を使用して数値を強制的にDouble
オブジェクトにすることができます。
jjs> var intNum = 10 jjs> intNum.class class java.lang.Integer jjs> var dblNum = Number(intNum) jjs> dblNum.class class java.lang.Double
Nashornは、Javaコレクションを配列として解釈します。次の例に示すように、角カッコ([]
)内のインデックスを使用してコレクションの要素にアクセスしたり、for each
文を使用してコレクションの値を反復処理したりできます。
jjs> var ArrayList = Java.type("java.util.ArrayList") jjs> var alist = new ArrayList() jjs> alist.add("a") true jjs> alist.add("b") true jjs> alist.add("c") true jjs> alist[1] b jjs> for each (var i in alist) print(i) a b c
マップ内のキーと値を反復処理するには、他のコレクションとは異なり、keySet()
およびvalues()
メソッドを使用する必要があります。次の例は、HashMap
オブジェクトを作成し、そのキーと値を反復処理する方法を示しています。
jjs> var HashMap = Java.type("java.util.HashMap") jjs> var hm = new HashMap() jjs> hm.put("name", "Bob") jjs> hm.put("age", 40) jjs> hm.put("weight", 180) jjs> for each (var i in hm.keySet()) print(i) weight age name jjs> for each (var i in hm.values()) print(i) 180 40 Bob
クラスを拡張するには、Java.extend()
関数を使用します。この関数は、最初の引数としてJava型を取り、他の引数として(JavaScript関数形式の)メソッド実装を取ります。例2-1は、java.lang.Runnable
インタフェースを拡張し、それを使用して新しいjava.lang.Thread
オブジェクトを構築するスクリプトを示しています。
例2-1 Javaクラスの拡張
var Run = Java.type("java.lang.Runnable"); var MyRun = Java.extend(Run, { run: function() { print("Run in separate thread"); } }); var Thread = Java.type("java.lang.Thread"); var th = new Thread(new MyRun());
メソッドを実装するための関数をコンストラクタの引数として指定すると、Nashornは自動的に単一抽象メソッド(SAM)クラスを拡張できます。例2-2は、java.lang.Runnable
インタフェースを拡張し、それを使用して新しいjava.lang.Thread
オブジェクトを構築するスクリプトを示していますが、Java.extend()関数がSAMクラスに対して自動的に呼び出されているため、例2-1
より使用するコードの行数が少なくなっています。
例2-2 Java SAMクラスの拡張
var Thread = Java.type("java.lang.Thread") var th = new Thread(function() print("Run in a separate thread"))
Java.extend()
関数の機能の詳細は、『Javaスクリプト・プログラマーズ・ガイド』(http://docs.oracle.com/javase/jp/8/technotes/guides/scripting/programmer_guide/index.html
)を参照してください
jdk.nashorn.api.scripting.ClassFilter
インタフェースhttps://docs.oracle.com/javase/8/docs/jdk/api/nashorn/jdk/api/scripting/ClassFilter.html
により、Nashornスクリプト・エンジンで実行されるスクリプトから指定したJavaクラスへのアクセスを制限できます。Nashornの、とりわけサーバー側JavaScriptフレームワークを埋め込んでいるアプリケーションは、信頼できないソースからスクリプトを実行しなければならないことがよくあり、そのためアクセスをJava APIに制限する必要があります。これらのアプリケーションは、ClassFilter
インタフェースを実装して、Javaクラス・アクセスをJavaクラスのサブセットに制限できます。それには、クライアント・アプリケーションでNashorn APIを使用して、Nashornスクリプト・エンジンをインスタンス化する必要があります。
クラス・フィルタを使用するには、ClassFilter
インタフェースを実装して、メソッドboolean exposeToScripts(String)
を定義します。String
引数は、Nashornスクリプト・エンジンがスクリプトの実行時に遭遇するJavaクラスまたはパッケージの名前です。メソッドexposeToScripts
を、スクリプトがアクセスしないようにするクラスとパッケージでfalse
を戻すように定義します。続いて、メソッドNashornScriptEngineFactory.getScriptEngine(ClassFilter)
またはNashornScriptEngineFactory.getScriptEngine(String[]、ClassLoader、ClassFilter)
を使用したクラス・フィルタを持つNashronスクリプト・エンジンを作成します。
セキュリティ・マネージャを使用している場合、Nashornでは、スクリプトは、nashorn.javaReflection
実行時権限を持っているときのみJavaリフレクションAPI(たとえば、java.lang.reflect
とjava.lang.invoke
パッケージ)を使用することができます。クラス・フィルタを使用している場合、セキュリティ・マネージャがない場合でも、NashornではJavaリフレクションAPIにアクセスできません。JavaリフレクションAPIが利用できる場合、スクリプトがClass.forName(String)
を使用してクラス・フィルタを回避できるため、クラス・フィルタを使用する必要はない点に注意してください。
次の例に、ClassFilter
インタフェースを示します。
例2-3 MyClassFilterTest.java
import javax.script.ScriptEngine; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class MyClassFilterTest { class MyCF implements ClassFilter { @Override public boolean exposeToScripts(String s) { if (s.compareTo("java.io.File") == 0) return false; return true; } } public void testClassFilter() { final String script = "print(java.lang.System.getProperty(\"java.home\"));" + "print(\"Create file variable\");" + "var File = Java.type(\"java.io.File\");"; NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine( new MyClassFilterTest.MyCF()); try { engine.eval(script); } catch (Exception e) { System.out.println("Exception caught: " + e.toString()); } } public static void main(String[] args) { MyClassFilterTest myApp = new MyClassFilterTest(); myApp.testClassFilter(); } }
この例の出力は次のとおりです。
C:\Java\jre8 Create file variable Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.io.File
MyClassFilterTest.java
の例では、次の操作を実行しています。
メソッドexposeToScripts
を定義することにより、内部クラスMyCF
を持つClassFilter
を実装します。この例で、メソッドexposeToScripts
は、String
引数がjava.io.File
の場合にfalseを戻し、それ以外の場合はtrue
を戻します。
メソッドNashornScriptEngineFactory.getScriptEngine(ClassFilter)
を使用して、Nashornスクリプト・エンジンengine
を作成します。この例は、MyCF
のインスタンスを持つgetScriptEgnine
を起動します。
Nashornスクリプト・エンジンengine
を使用して、次のスクリプトを実行します。
print(java.lang.System.getProperty(\"java.home\")); print(\"Create file variable\"); var File = Java.type(\"java.io.File\");
engine
のクラス・フィルタは、スクリプト内の各Javaパッケージおよびクラスをチェックします。クラス・フィルタがクラスjava.io.File
に遭遇すると、クラス・フィルタはfalse
を戻し、Java.type
関数はClassNotFoundException
を送出します。