この項には、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つの文字列を連結すると、Stringインスタンスを取得します。
jjs> var a = "abc" jjs> a.class class java.lang.String jjs> var b = a + "def" jjs> b.class class java.lang.String 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文でリストの値を反復処理します。マップでキーおよび値を反復処理するには、keySet()およびvalues()メソッドを使用します。
次の例に示すように、角カッコ([])内のインデックスを使用してリストの要素にアクセスしたり、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
注意:
角カッコ内のインデックスを使用してリストおよびマップ要素にアクセスできますが、この構文を使用してキューおよび設定要素にはアクセスできません。次の例は、HashMapオブジェクトを作成し、keySet()およびvalues()メソッドを使用してそのキーと値を反復処理する方法を示しています。
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
または、次の例に示すように、リストの値の反復処理と同様に、マップの値を反復処理することもできます。
jjs> for each (var i in hm) print(i)
180
40
Bob
クラスを拡張するには、Java.extend()関数を使用します。この関数は、最初の引数としてJava型を取り、他の引数として(JavaScript関数形式の)メソッド実装を取ります。
次のスクリプトは、java.lang.Runnableインタフェースを拡張し、それを使用して新しいjava.lang.Threadオブジェクトを構築しています。
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は自動的に機能インタフェース(注釈タイプFunctionalInterfaceに関する項を参照)を拡張できます。次のスクリプトは、java.lang.Runnableインタフェースを拡張し、それを使用して新しいjava.lang.Threadオブジェクトを構築するスクリプトを示していますが、Java.extend()関数が機能インタフェースに対して自動的に呼び出されているため、例2-1より使用するコードの行数が少なくなっています。
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
『Java Platform, Standard Edition Java Scriptingプログラマ・ガイド』のJavaクラスの拡張に関する項では、Java.extend()関数の機能について説明しています。
例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());
例2-2 機能インタフェースの拡張
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
jdk.nashorn.api.scripting.ClassFilterインタフェースにより、Nashornスクリプト・エンジンで実行されるスクリプトから指定したJavaクラスへのアクセスを制限することで、JavaScriptコードからJavaクラスへのアクセスに対してきめの細かい制御を提供できます。
Nashornの、とりわけサーバー側JavaScriptフレームワークを埋め込んでいるアプリケーションは、信頼できないソースからスクリプトを実行しなければならないことがよくあり、そのためアクセスをJava APIに制限する必要があります。これらのアプリケーションは、ClassFilterインタフェースを実装して、Javaクラス・アクセスをJavaクラスのサブセットに制限できます。それには、クライアント・アプリケーションでNashorn APIを使用して、Nashornスクリプト・エンジンをインスタンス化する必要があります。
注意:
ClassFilterインタフェースでJavaクラスへのアクセスを防止できますが、信頼できないスクリプトをセキュアに実行するには不十分です。ClassFilterインタフェースはセキュリティ・マネージャのかわりにはなりません。信頼できないソースのスクリプトを評価する前に、セキュリティ・マネージャを使用してアプリケーションを実行する必要があります。クラス・フィルタは、セキュリティ・マネージャよりもきめの細かい制御を提供します。たとえば、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)を使用してクラス・フィルタを回避できるため、クラス・フィルタを使用する必要はない点に注意してください。
次の例のMyClassFilterTest.javaに、ClassFilterインタフェースを示します。スクリプトがクラスjava.io.Fileにアクセスするのを制限しています。
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の例では、次の操作を実行しています。