Javaの相互運用性
GraalVMにはJavaScript言語実行ランタイムが含まれており、Javaコードとの相互運用が可能です。このドキュメントでは、JavaScriptとJavaの相互運用性機能とその使用方法について説明します。
GraalVMパブリックAPIのリファレンスは、「JavaScriptの互換性」を参照してください。Javaホスト・アプリケーションからJavaScriptなどのゲスト言語と対話する方法については、埋込みリファレンスを参照してください。ポリグロット・プログラミング・ガイドは、その領域の追加のヘルプを提供できます。RhinoおよびNashornの移行ガイドも用意されています。
JavaScriptとNode.jsはどちらも、GraalVMの個別にインストール可能なコンポーネントです。GraalVMアップデータ・ツールgu
を使用してJavaScriptおよびNode.jsをインストールする方法の詳細は、READMEを参照してください。インストールが成功すると、$GRAALVM/bin
ディレクトリにあるそれぞれのネイティブ・ランチャjs
およびnode
を使用できます。
他のビルドも可能ですが、次の各例では、この設定が使用されていることを前提としています。
Javaの相互運用性の有効化
GraalVMでは、js
およびnode
ランチャは、デフォルトで事前コンパイル済ネイティブ・モードで起動されます。このモードでは、Javaの相互運用性は使用できません。
Javaの相互運用性を有効にするには、ネイティブ・ランチャに--jvm
オプションを渡す必要があります。これにより、GraalVM JavaScriptは従来のJVMで実行され、Javaの完全な相互運用性が実現されます。
クラスパス
Javaクラスをロードするには、それらをJavaクラスパスに含める必要があります。クラスパスは、--vm.classpath=<classpath>
オプション(または短縮形の--vm.cp=<classpath>
)を使用して指定できます:
node --jvm --vm.cp=/my/class/path
js --jvm --vm.cp=/my/class/path
Java.addToClasspath()
メソッドを使用すると、実行時にプログラムでクラスパスに追加できます。
ポリグロット・コンテキスト
Java相互運用性サポートを使用してGraalVM JavaScriptを起動するには、ポリグロットContext
を使用することをお薦めします。その際には、hostAccess
オプション(アクセス許可に使用)とhostClassLookup
述語(アクセスを許可するJavaクラスの定義に使用)に基づいて、新しいorg.graalvm.polyglot.Context
が構築されます:
Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.ALL)
//allows access to all Java classes
.allowHostClassLookup(className -> true)
.build();
context.eval("js", jsSourceCode);
Javaホスト・アプリケーションからJavaScriptなどのゲスト言語と対話する方法については、埋込みリファレンスを参照してください。ポリグロット・プログラミング・ガイドは、その領域のヘルプも提供できます。
ScriptEngine (JSR 223)
org.graalvm.polyglot.Context
は、GraalVMの言語およびツールとの相互運用性確保に推奨される実行方法です。また、GraalVMで実行されるJavaScriptはJSR 223と完全に互換性があり、ScriptEngine API
をサポートしています。内部的には、GraalVMのJavaScript ScriptEngineによってポリグロット・コンテキスト・インスタンスがラップされます:
ScriptEngine eng = new ScriptEngineManager()
.getEngineByName("graal.js");
Object fn = eng.eval("(function() { return this; })");
Invocable inv = (Invocable) eng;
Object result = inv.invokeMethod(fn, "call", fn);
GraalVM JavaScriptでの使用方法の詳細は、ScriptEngineガイドを参照してください。
JavaScriptからのJavaへのアクセス
GraalVMには、JavaScript
からJava
への相互運用性を実現する一連の機能が用意されています。Rhino、NashornおよびGraalVM JavaScriptの全体的な機能セットはほぼ同等ですが、厳密な構文およびセマンティクスの一部が異なります。
クラスへのアクセス
Javaクラスにアクセスできるように、GraalVM JavaScriptはJava.type(typeName)
関数をサポートしています:
var FileClass = Java.type('java.io.File');
デフォルトでは、Javaクラスはグローバル変数に自動的にはマップされません。たとえば、GraalVM JavaScriptにはjava
グローバル・プロパティはありません。既存のコード・アクセス(java.io.File
など)は、Java.type(name)
関数を使用するように書き換える必要があります:
//GraalVM JavaScript compliant syntax
var FileClass = Java.type("java.io.File");
//backwards-compatible syntax
var FileClass = java.io.File;
GraalVM JavaScriptには、互換性確保のためにPackages
、java
および類似のグローバル・プロパティが用意されています。ただし、次の2つの理由から、可能な場合は常にJava.type
を使用して目的のクラスに明示的にアクセスすることをお薦めします:
- これにより、各プロパティをクラスとして解決しようとするのではなく、1つのステップでクラスを解決できます。
Java.type
は、クラスが見つからないか、アクセスできない場合に、未解決の名前をパッケージとしてサイレントに処理するのではなく、TypeError
をただちにスローします。
js.java-package-globals
フラグを使用すると、Javaパッケージのグローバル・フィールドを非アクティブ化できます(フィールドの作成を回避するにはfalse
を設定します。デフォルトはtrue
です)。
Javaオブジェクトの構築
Javaオブジェクトは、JavaScriptのnew
キーワードを使用して構成できます:
var FileClass = Java.type('java.io.File');
var file = new FileClass("myFile.md");
フィールドおよびメソッドへのアクセス
Javaクラスの静的フィールドまたはJavaオブジェクトのフィールドには、JavaScriptプロパティと同様にアクセスできます:
var JavaPI = Java.type('java.lang.Math').PI;
Javaメソッドは、JavaScript関数と同様にコールできます:
var file = new (Java.type('java.io.File'))("test.md");
var fileName = file.getName();
メソッドの引数の変換
JavaScriptは、double
数値型で動作するように定義されています。GraalVM JavaScriptでは、パフォーマンス上の理由から、内部的に追加のJavaデータ型(int
型など)を使用する場合があります。
Javaメソッドをコールする際には、値の変換が必要になることがあります。これは、Javaメソッドがlong
パラメータを必要とし、GraalVM JavaScriptからint
が渡された場合に起こります(型の拡大
)。この変換によって非可逆変換が発生すると、TypeError
がスローされます:
//Java
void longArg (long arg1);
void doubleArg (double arg2);
void intArg (int arg3);
//JavaScript
javaObject.longArg(1); //widening, OK
javaObject.doubleArg(1); //widening, OK
javaObject.intArg(1); //match, OK
javaObject.longArg(1.1); //lossy conversion, TypeError!
javaObject.doubleArg(1.1); //match, OK
javaObject.intArg(1.1); //lossy conversion, TypeError!
引数値がパラメータ型に収まる必要があることに注意してください。この動作は、カスタムのターゲット・タイプ・マッピングを使用してオーバーライドできます。
メソッドの選択
Javaでは、引数の型によってメソッドをオーバーロードできます。JavaScriptからのJavaのコール時には、可能なかぎり狭く、実際の引数を損失なしで変換できる型を持つメソッドが選択されます:
//Java
void foo(int arg);
void foo(short arg);
void foo(double arg);
void foo(long arg);
//JavaScript
javaObject.foo(1); // will call foo(short);
javaObject.foo(Math.pow(2,16)); // will call foo(int);
javaObject.foo(1.1); // will call foo(double);
javaObject.foo(Math.pow(2,32)); // will call foo(long);
この動作をオーバーライドするには、javaObject['methodName(paramTypes)']
構文を使用して明示的なメソッドのオーバーロードを選択できます。パラメータ型はスペースなしでカンマで区切る必要があります。オブジェクト型は完全修飾する必要があります(例: 'get(java.lang.String,java.lang.String[])'
)。これはNashornとは異なり、余分なスペースや単純な名前を使用できることに注意してください。前述の例では、可逆変換(foo(1)
)でfoo(short)
に到達できたとしても、場合によっては常にfoo(long)
をコールする必要があります。
javaObject['foo(int)'](1);
javaObject['foo(long)'](1);
javaObject['foo(double)'](1);
引数値は引き続きパラメータ型に収まる必要があることに注意してください。この動作は、カスタムのターゲット・タイプ・マッピングを使用してオーバーライドできます。
明示的なメソッドの選択は、メソッドのオーバーロードがあいまいで自動的に解決できない場合や、デフォルトの選択をオーバーライドする場合にも役立ちます:
//Java
void sort(List<Object> array, Comparator<Object> callback);
void sort(List<Integer> array, IntBinaryOperator callback);
void consumeArray(List<Object> array);
void consumeArray(Object[] array);
//JavaScript
var array = [3, 13, 3, 7];
var compare = (x, y) => (x < y) ? -1 : ((x == y) ? 0 : 1);
// throws TypeError: Multiple applicable overloads found
javaObject.sort(array, compare);
// explicitly select sort(List, Comparator)
javaObject['sort(java.util.List,java.util.Comparator)'](array, compare);
// will call consumeArray(List)
javaObject.consumeArray(array);
// explicitly select consumeArray(Object[])
javaObject['consumeArray(java.lang.Object[])'](array);
現在、コンストラクタのオーバーロードを明示的に選択する方法はありません。GraalVM JavaScriptの今後のバージョンでは、この制限が解消される可能性があります。
パッケージへのアクセス
GraalVM JavaScriptには、Packages
グローバル・プロパティが用意されています:
> Packages.java.io.File
JavaClass[java.io.File]
配列へのアクセス
GraalVM JavaScriptは、JavaScriptコードからのJava配列の作成をサポートしています。RhinoとNashornによって提案される両方のパターンがサポートされています:
//Rhino pattern
var JArray = Java.type('java.lang.reflect.Array');
var JString = Java.type('java.lang.String');
var sarr = JArray.newInstance(JString, 5);
//Nashorn pattern
var IntArray = Java.type("int[]");
var iarr = new IntArray(5);
作成される配列はJava型ですが、JavaScriptコードで使用できます:
iarr[0] = iarr[iarr.length] * 2;
マップへのアクセス
GraalVM JavaScriptでは、Javaマップ(java.util.HashMap
など)を作成してアクセスできます:
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put(1, "a");
map.get(1);
GraalVM JavaScriptは、Nashornと同様に、このようなマップの反復をサポートしています:
for (var key in map) {
print(key);
print(map.get(key));
}
リストへのアクセス
GraalVM JavaScriptでは、Javaリスト(java.util.ArrayList
など)を作成してアクセスできます:
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add(42);
list.add("23");
list.add({});
for (var idx in list) {
print(idx);
print(list.get(idx));
}
文字列へのアクセス
GraalVM JavaScriptでは、Javaの相互運用性を備えたJava文字列を作成できます。文字列の長さは、length
プロパティを使用して問い合せることができます(length
は値プロパティであり、関数としてコールすることはできません):
var javaString = new (Java.type('java.lang.String'))("Java");
javaString.length === 4;
GraalVM JavaScriptでは、内部的にJava文字列を使用してJavaScript文字列が表されるため、前述のコードとJavaScript文字列リテラル"Java"
は実際には区別できません。
プロパティの反復
JavaクラスおよびJavaオブジェクトのプロパティ(フィールドおよびメソッド)は、JavaScriptのfor..in
ループを使用して反復できます:
var m = Java.type('java.lang.Math')
for (var i in m) { print(i); }
> E
> PI
> abs
> sin
> ...
JavaからのJavaScriptオブジェクトへのアクセス
JavaScriptオブジェクトは、com.oracle.truffle.api.interop.java.TruffleMap
のインスタンスとしてJavaコードに公開されます。このクラスにより、JavaのMap
インタフェースが実装されます。
JavaImporter
JavaImporter
機能は、Nashorn互換性モード(js.nashorn-compat
オプション)でのみ使用できます。
JavaクラスおよびJavaオブジェクトのコンソール出力
GraalVM JavaScriptは、print
とconsole.log
の両方に対応しています。
GraalVM JavaScriptには、Nashornと互換性のあるprint
組込み関数が用意されています。
console.log
は、Node.jsによって直接提供されます。相互運用性オブジェクトの特別な処理は行われません。GraalVM JavaScriptでのconsole.log
のデフォルト実装は単にprint
の別名であり、ノードの実装はNode.jsでの実行時にのみ使用できることに注意してください。
例外
Javaコードでスローされた例外は、JavaScriptコードで捕捉できます。これらはJavaオブジェクトとして表されます:
try {
Java.type('java.lang.Class')
.forName("nonexistent");
} catch (e) {
print(e.getMessage());
}
Promise
GraalVM JavaScriptは、JavaScript Promise
オブジェクトとJavaの間の相互運用性をサポートしています。JavaオブジェクトをthenableなオブジェクトとしてJavaScriptコードに公開することによって、await
Javaオブジェクトに対するJavaScriptコードを許可できます。さらに、JavaScript Promise
オブジェクトは通常のJavaScriptオブジェクトであり、このドキュメントで説明するメカニズムを使用してJavaからアクセスできます。これにより、JavaScriptのPromiseが解決または拒否されたときに、JavaScriptからJavaコードをコールバックできます。
Javaから解決可能なJavaScript Promise
オブジェクトの作成
JavaScriptアプリケーションでは、Promise
インスタンスの解決をJavaに委任するPromise
オブジェクトを作成できます。これは、JavaオブジェクトをJavaScript Promise
のexecutor機能として使用することで、JavaScriptから実現できます。たとえば、次の関数型インタフェースを実装するJavaオブジェクトを使用して、新しいPromise
オブジェクトを作成できます:
@FunctionalInterface
public interface PromiseExecutor {
void onPromiseCreation(Value onResolve, Value onReject);
}
PromiseExecutor
を実装する任意のJavaオブジェクトを使用して、JavaScript Promise
を作成できます:
// `javaExecutable` is a Java object implementing the `PromiseExecutor` interface
var myPromise = new Promise(javaExecutable).then(...);
JavaScript Promise
オブジェクトは、関数型インタフェースだけでなく、GraalVM JavaScriptエンジンで実行できる他のJavaオブジェクト(たとえば、ポリグロットProxyExecutableインタフェースを実装する任意のJavaオブジェクト)を使用しても作成できます。使用例の詳細は、GraalVM JavaScriptユニット・テストを参照してください。
Javaオブジェクトでのawait
の使用
JavaScriptアプリケーションでは、Javaオブジェクトでawait
式を使用できます。これは、JavaおよびJavaScriptが非同期イベントと対話する必要がある場合に便利です。JavaオブジェクトをthenableなオブジェクトとしてGraalVM JavaScriptに公開するには、Javaオブジェクトに次のシグネチャを持つthen()
というメソッドを実装する必要があります:
void then(Value onResolve, Value onReject);
then()
を実装するJavaオブジェクトでawait
を使用すると、そのオブジェクトはGraalVM JavaScriptランタイムでJavaScript Promise
として扱われます。onResolve
およびonReject
引数は、対応するJavaオブジェクトに関連付けられたJavaScript await
式を再開または中断するためにJavaコードで使用される実行可能なValue
オブジェクトです。使用例の詳細は、GraalVM JavaScriptユニット・テストを参照してください。
JavaからのJavaScript Promiseの使用
JavaScriptで作成されたPromise
オブジェクトは、他のJavaScriptオブジェクトと同様にJavaコードに公開できます。Javaコードでは、通常のValue
オブジェクトと同様にこれらのオブジェクトにアクセスでき、Promise
のデフォルトのthen()
およびcatch()
関数を使用して新しいPromise解決関数を登録できます。たとえば、次のJavaコードでは、JavaScript Promiseの解決時に実行されるJavaコールバックが登録されます:
Value jsPromise = context.eval(ID, "Promise.resolve(42);");
Consumer<Object> javaThen = (value)
-> System.out.println("Resolved from JavaScript: " + value);
jsPromise.invokeMember("then", javaThen);
使用例の詳細は、GraalVM JavaScriptユニット・テストを参照してください。
マルチスレッド
GraalVM JavaScriptは、Javaと組み合せて使用する場合にマルチスレッドをサポートしています。GraalVM JavaScriptマルチスレッド・モデルの詳細は、「マルチスレッド」ドキュメントを参照してください。
Javaクラスの拡張
JVMモード(--jvm
)では、GraalVM JavaScriptによって、Java.extend
関数を使用してJavaクラスおよびインタフェースを拡張するためのサポートが提供されます。この機能を使用可能にするには、ポリグロット・コンテキストでホスト・アクセスを有効にする必要があります。
Java.extend
Java.extend(types...)
は、生成されたアダプタのJavaクラス・オブジェクトを返します。指定されたJavaクラスまたはインタフェース(あるいは両方)がこれによって拡張されます。たとえば:
var Ext = Java.extend(Java.type("some.AbstractClass"),
Java.type("some.Interface1"),
Java.type("some.Interface2"));
var impl = new Ext({
superclassMethod: function() {/*...*/},
interface1Method: function() {/*...*/},
interface2Method: function() {/*...*/},
toString() {return "MyClass";}
});
impl.superclassMethod();
スーパー・メソッドは、Java.super(adapterInstance)
を介してコールできます。組合せの例を参照してください:
var sw = new (Java.type("java.io.StringWriter"));
var FilterWriterAdapter = Java.extend(Java.type("java.io.FilterWriter"));
var fw = new FilterWriterAdapter(sw, {
write: function(s, off, len) {
s = s.toUpperCase();
if (off === undefined) {
fw_super.write(s, 0, s.length)
} else {
fw_super.write(s, off, len)
}
}
});
var fw_super = Java.super(fw);
fw.write("abcdefg");
fw.write("h".charAt(0));
fw.write("**ijk**", 2, 3);
fw.write("***lmno**", 3, 4);
print(sw); // ABCDEFGHIJKLMNO
nashorn-compat
モードでは、新しい演算子をインタフェースまたは抽象クラスの型オブジェクトに対して使用して、インタフェースと抽象クラスを拡張することもできます:
// --experimental-options --js.nashorn-compat
var JFunction = Java.type('java.util.function.Function');
var sqFn = new JFunction({
apply: function(x) { return x * x; }
});
sqFn.apply(6); // 36