Java Platform, Standard Edition Nashornユーザーズ・ガイド
目次      

2 Nashorn Java API

この章では、Nashornエンジンで解釈されるスクリプトからJavaにアクセスする方法について説明します。

この章の各項には、Nashornエンジンの対話型言語シェル・モードで解釈されるスクリプト文の例が含まれています。この対話型シェルは、スクリプトを指定せずにjjsコマンドを実行すると起動されます。これは何かを試す場合に便利ですが、Nashorn Java APIの主な目的は、JavaアプリケーションをNashornエンジンで解釈できるスクリプトとして作成することです。

Javaクラスへのアクセス

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パッケージ(comedujavajavafxjavaxおよび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]

Javaオブジェクトの作成

クラスをインスタンス化するには、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

JavaBeansの使用

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構文と同様の)オブジェクトの型を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

Javaクラスの拡張

クラスを拡張するには、Java.extend()関数を使用します。この関数は、最初の引数としてJava型を取り、他の引数として(JavaScript関数形式の)メソッド実装を取ります。例2-1は、java.lang.Runnableインタフェースを拡張し、それを使用して新しいjava.lang.Threadオブジェクトを構築するスクリプトを示しています。

メソッドを実装するための関数をコンストラクタの引数として指定すると、Nashornは自動的に単一抽象メソッド(SAM)クラスを拡張できます。例2-2は、java.lang.Runnableインタフェースを拡張し、それを使用して新しいjava.lang.Threadオブジェクトを構築するスクリプトを示していますが、Java.extend()関数がSAMクラスに対して自動的に呼び出されているため、例2-1より使用するコードの行数が少なくなっています。

Java.extend()関数の機能の詳細は、『Javaスクリプト・プログラマーズ・ガイド』(http://docs.oracle.com/javase/jp/8/technotes/guides/scripting/programmer_guide/index.html)を参照してください

指定したJavaクラスへのスクリプト・アクセスの制限

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スクリプト・エンジンを作成します。

JavaリフレクションAPIへのアクセスの制限

セキュリティ・マネージャを使用している場合、Nashornでは、スクリプトは、nashorn.javaReflection実行時権限を持っているときのみJavaリフレクションAPI(たとえば、java.lang.reflectjava.lang.invokeパッケージ)を使用することができます。クラス・フィルタを使用している場合、セキュリティ・マネージャがない場合でも、NashornではJavaリフレクションAPIにアクセスできません。JavaリフレクションAPIが利用できる場合、スクリプトがClass.forName(String)を使用してクラス・フィルタを回避できるため、クラス・フィルタを使用する必要はない点に注意してください。

ClassFilterインタフェースの使用例

次の例に、ClassFilterインタフェースを示します。

この例の出力は次のとおりです。

C:\Java\jre8
Create file variable
Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException:
java.io.File

MyClassFilterTest.javaの例では、次の操作を実行しています。

  1. メソッドexposeToScriptsを定義することにより、内部クラスMyCFを持つClassFilterを実装します。この例で、メソッドexposeToScriptsは、String引数がjava.io.Fileの場合にfalseを戻し、それ以外の場合はtrueを戻します。

  2. メソッドNashornScriptEngineFactory.getScriptEngine(ClassFilter)を使用して、Nashornスクリプト・エンジンengineを作成します。この例は、MyCFのインスタンスを持つgetScriptEgnineを起動します。

  3. 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を送出します。

目次      

Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.