- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (早期アクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
Java 相互運用性
このドキュメントでは、Java との相互運用を有効にする方法と、JavaScript から Java への組み込みのシナリオについて説明します。
Java 相互運用性の有効化 #
GraalVM for JDK 21 以降では、必要なすべての成果物を Maven Central から直接ダウンロードできます。埋め込みに関連するすべての成果物は、Maven 依存関係グループ org.graalvm.polyglot
にあります。依存関係の設定の詳細については、入門ガイドをご覧ください。
ポリグロットコンテキスト #
Java に JavaScript を埋め込む推奨方法は、Context
を使用することです。そのため、アクセスを許可する hostAccess
オプションと、アクセスを許可する Java クラスを定義する hostClassLookup
述語を使用して、新しい 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) #
GraalVM JDK で実行されている JavaScript は JSR 223 と完全に互換性があり、ScriptEngine API
をサポートしています。内部的には、GraalVM の JavaScript ScriptEngine はポリグロット Context
インスタンスをラップします。
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);
GraalJS からの使用方法の詳細については、ScriptEngine ガイドをご覧ください。
JavaScript から Java へのアクセス #
GraalVM には、JavaScript
から Java
への相互運用を可能にする一連の機能が用意されています。Rhino、Nashorn、GraalJS は、全体的な機能セットはほぼ同等ですが、正確な構文、および部分的にセマンティクスが異なります。
クラスアクセス #
Java クラスにアクセスするために、GraalJS は Java.type(typeName)
関数をサポートしています。
var FileClass = Java.type('java.io.File');
ホストクラスの検索が許可されている場合 (allowHostClassLookup
)、java
グローバルプロパティがデフォルトで使用できます。たとえば、java.io.File
にアクセスする既存のコードは、Java.type(name)
関数を使用するように書き換える必要があります。
// GraalJS (and Nashorn) compliant syntax
var FileClass = Java.type("java.io.File");
// Backwards-compatible syntax
var FileClass = java.io.File;
GraalJS は、互換性のために 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
数値型で動作するように定義されています。GraalJS は、パフォーマンス上の理由から、内部的に追加の Java データ型 (たとえば、int
型) を使用する場合があります。
Java メソッドを呼び出す場合は、値の変換が必要になる場合があります。これは、Java メソッドが long
パラメータを必要とし、GraalJS から 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(short)
に到達できる場合でも、常に foo(long)
を呼び出すことが望ましい場合があります (foo(1)
)
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);
現在、コンストラクターのオーバーロードを明示的に選択する方法がないことに注意してください。GraalJS の今後のバージョンでは、その制限が解除される可能性があります。
パッケージアクセス #
GraalJS は、Packages
グローバルプロパティを提供します。
> Packages.java.io.File
JavaClass[java.io.File]
配列アクセス #
GraalJS は、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;
マップアクセス #
GraalJS では、java.util.HashMap
などの Java マップを作成およびアクセスできます。
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put(1, "a");
map.get(1);
GraalJS は、Nashorn と同様に、このようなマップの反復処理をサポートしています。
for (var key in map) {
print(key);
print(map.get(key));
}
リストアクセス #
GraalJS では、java.util.ArrayList
などの Java リストを作成およびアクセスできます。
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));
}
文字列アクセス #
GraalJS は、Java 相互運用性を使用して Java 文字列を作成できます。文字列の長さは、length
プロパティでクエリできます (length
は値プロパティであり、関数として呼び出すことはできません)。
var javaString = new (Java.type('java.lang.String'))("Java");
javaString.length === 4;
GraalJS は JavaScript 文字列を表すために内部的に Java 文字列を使用しているため、上記のコードと 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 オブジェクトは、Java コードに対して com.oracle.truffle.api.interop.java.TruffleMap
のインスタンスとして公開されます。このクラスは Java の Map
インターフェイスを実装します。
JavaImporter #
JavaImporter
機能は、Nashorn 互換モード (js.nashorn-compat
オプションを使用) でのみ使用可能です。
Java クラスおよび Java オブジェクトのコンソール出力 #
GraalJS は、print
と console.log
の両方を提供します。
GraalJS は、Nashorn と互換性のある print
組み込み関数を提供します。
console.log
は、Node.js によって直接提供されます。相互運用オブジェクトの特別な扱いを提供しません。GraalJS での console.log
のデフォルト実装は print
のエイリアスにすぎず、Node の実装は Node.js で実行する場合にのみ使用できることに注意してください。
例外 #
Java コードでスローされた例外は、JavaScript コードでキャッチできます。それらは Java オブジェクトとして表現されます。
try {
Java.type('java.lang.Class')
.forName("nonexistent");
} catch (e) {
print(e.getMessage());
}
プロミス #
GraalJS は、JavaScript Promise
オブジェクトと Java 間の相互運用性のサポートを提供します。Java オブジェクトは、JavaScript コードに対して thenable オブジェクトとして公開できるため、JavaScript コードは Java オブジェクトを await
できます。さらに、JavaScript Promise
オブジェクトは通常の JavaScript オブジェクトであり、このドキュメントで説明されているメカニズムを使用して Java からアクセスできます。これにより、JavaScript プロミスが解決または拒否されたときに、Java コードを JavaScript からコールバックできます。
Java から解決できる JavaScript Promise
オブジェクトの作成 #
JavaScript アプリケーションは、Promise
インスタンスの解決を Java に委任する Promise
オブジェクトを作成できます。これは、JavaScript から Java オブジェクトを JavaScript Promise
の 「executor」 関数として使用することで実現できます。たとえば、次の関数型インターフェイスを実装する 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
オブジェクトは、関数型インターフェースを使用して作成できるだけでなく、GraalJS で実行可能な他の任意の Java オブジェクト(例えば、Polyglot の ProxyExecutable インターフェースを実装する任意の Java オブジェクトなど)を使用して作成することもできます。詳細な使用例は、GraalJS の 単体テスト で確認できます。
Java オブジェクトでの await
の使用 #
JavaScript アプリケーションは、Java オブジェクトに対して await
式を使用できます。これは、Java と JavaScript が非同期イベントとやり取りする必要がある場合に役立ちます。Java オブジェクトを thenable オブジェクトとして GraalJS に公開するには、Java オブジェクトは次のシグネチャを持つ then()
という名前のメソッドを実装する必要があります。
void then(Value onResolve, Value onReject);
then()
を実装する Java オブジェクトで await
を使用すると、GraalJS はそのオブジェクトを JavaScript の Promise
として扱います。onResolve
および onReject
引数は、Java コードが対応する Java オブジェクトに関連付けられた JavaScript の await
式を再開または中止するために使用する必要がある実行可能な Value
オブジェクトです。詳細な使用例は、GraalJS の 単体テスト で確認できます。
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);
詳細な使用例は、GraalJS の 単体テスト で確認できます。
マルチスレッド #
GraalJS は、Java と組み合わせて使用する場合、マルチスレッドをサポートしています。GraalJS のマルチスレッドモデルの詳細については、マルチスレッドのドキュメントを参照してください。
Java クラスの拡張 #
GraalJS は、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
モードでは、インターフェースまたは抽象クラスの型オブジェクトで new 演算子を使用して、インターフェースおよび抽象クラスを拡張することもできます。
// --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