Java Platform, Standard Edition Javaスクリプト・プログラマーズ・ガイド
目次      

3 スクリプトからのJavaの使用

この章では、スクリプトからJavaクラスおよびインタフェースにアクセスする方法について説明します。

コード例はJavaScriptで作成されていますが、JSR 223に準拠する任意のスクリプト言語を使用できます。例をスクリプト・ファイルとして使用したり、例の式を対話型シェルで1つずつ実行したりできます。オブジェクトのフィールドやメソッドにアクセスする構文は、JavaScriptでもJavaでも同じです。

この章には、次のセクションがあります。

3.1 Javaクラスへのアクセス

JavaScriptからJavaのプリミティブ型および参照型にアクセスするには、Java.type()関数を呼び出します。この関数は、文字列として渡されたクラスの完全な名前に対応する型オブジェクトを返します。次の例は、様々な型オブジェクトを取得する方法を示しています。

var ArrayList = Java.type("java.util.ArrayList");
var intType = Java.type("int");
var StringArrayType = Java.type("java.lang.String[]");
var int2DArrayType = Java.type("int[][]");

Java.type()関数から返された型オブジェクトは、Javaでクラス名を使用する場合と同じようにJavaScriptで使用できます。たとえば、次のように新しいオブジェクトをインスタンス化するために使用できます。

var anArrayList = new Java.type("java.util.ArrayList");

Java型オブジェクトを使用して、新しいJavaオブジェクトをインスタンス化できます。次の例は、新しいオブジェクトをインスタンス化するときに、デフォルト・コンストラクタを使用する方法と、別のコンストラクタに引数を渡す方法を示しています。

var ArrayList = Java.type("java.util.ArrayList");
var defaultSizeArrayList = new ArrayList;
var customSizeArrayList = new ArrayList(16);

Java.type()関数から返された型オブジェクトを使用して、次のようにstaticフィールドおよびメソッドにアクセスできます。

var File = Java.type("java.io.File");
File.createTempFile("nashorn", ".tmp");

static内部クラスにアクセスするには、Java.type()メソッドに渡す引数でドル記号($)を使用します。次の例は、java.awt.geom.Arc2DFloat内部クラスの型オブジェクトを返す方法を示しています。

var Float = Java.type("java.awt.geom.Arc2D$Float");

外部クラスの型オブジェクトがすでにある場合は、次のように外部クラスのプロパティとして内部クラスにアクセスできます。

var Arc2D = Java.type("java.awt.geom.Arc2D")
var Float = Arc2D.Float

staticでない内部クラスの場合は、内部クラスのインスタンスをコンストラクタの最初の引数として渡す必要があります。

JavaScriptの型オブジェクトは、Javaクラスと同じように使用できますが、getClass()メソッドから返されるjava.lang.Classオブジェクトとは区別されます。classおよびstaticプロパティを使用して一方からもう一方を取得できます。次の例は、この区別を示しています。

var ArrayList = Java.type("java.util.ArrayList");
var a = new ArrayList;

// All of the following are true:
print("Type acts as target of instanceof: " + (a instanceof ArrayList));
print("Class doesn't act as target of instanceof: " + !(a instanceof a.getClass()));
print("Type is not the same as instance's getClass(): " + (a.getClass() !== ArrayList));
print("Type's `class` property is the same as instance's getClass(): " + (a.getClass() === ArrayList.class));
print("Type is the same as the `static` property of the instance's getClass(): " + (a.getClass().static === ArrayList));

このコンパイル時のクラス式と実行時のクラス・オブジェクトを区別する点で、JavaScriptとJavaコードは構文的にも意味的にも似ています。ただし、Javaではコンパイル時のクラス式がオブジェクトとして表現されることはないため、Classオブジェクトのstaticプロパティに相当するものはありません。

3.2 Javaパッケージおよびクラスのインポート

Javaクラスに単純名でアクセスするには、importPackage()およびimportClass()関数を使用してJavaパッケージおよびクラスをインポートします。これらの関数は、互換性スクリプト(mozilla_compat.js)に組み込まれています。次の例は、importPackage()およびimportClass()関数の使用方法を示しています。

// Load compatibility script
load("nashorn:mozilla_compat.js");
// Import the java.awt package
importPackage(java.awt);
// Import the java.awt.Frame class
importClass(java.awt.Frame);
// Create a new Frame object
var frame = new java.awt.Frame("hello");
// Call the setVisible() method
frame.setVisible(true);
// Access a JavaBean property
print(frame.title);

JavaパッケージにアクセスするにはPackagesグローバル変数(たとえば、Packages.java.util.VectorPackages.javax.swing.JFrame)を使用しますが、標準Java SEパッケージにはショートカット(Packages.javaを表すjavaPackages.javaxを表すjavaxPackages.orgを表すorg)があります。

java.langパッケージは、そのクラスがObjectBooleanMathおよびその他の組込みJavaScriptオブジェクトと競合するため、デフォルトではインポートされません。さらに、Javaパッケージまたはクラスをインポートすると、JavaScriptのグローバル変数スコープと競合する可能性があります。これを避けるには、次の例に示すように、JavaImporterオブジェクトを定義し、with文を使用してインポートされたJavaパッケージおよびクラスのスコープを制限します。

// Create a JavaImporter object with specified packages and classes to import
var Gui = new JavaImporter(java.awt, javax.swing);

// Pass the JavaImporter object to the "with" statement and access the classes
// from the imported packages by their simple names within the statement's body
with (Gui) {
    var awtframe = new Frame("AWT Frame");
    var jframe = new JFrame("Swing JFrame");
};

3.3 Java配列の使用

Java配列オブジェクトを作成するには、最初にJava配列型オブジェクトを取得し、次にそれをインスタンス化する必要があります。JavaScriptで配列要素とlengthプロパティにアクセスする構文は、次の例に示すように、Javaと同じです。

var StringArray = Java.type("java.lang.String[]");
var a = new StringArray(5);

// Set the value of the first element
a[0] = "Scripting is great!";
// Print the length of the array
print(a.length);
// Print the value of the first element
print(a[0]);

JavaScript配列を取得したら、Java.to()メソッドを使用してそれをJava配列に変換できます。このメソッドには、JavaScript配列変数を渡し、さらに返される配列の型を文字列または型オブジェクトとして渡す必要があります。配列型引数を省略して、Object[]配列を返すこともできます。変換は、ECMAScriptの変換規則に従って行われます。次の例は、様々なJava.to()メソッドの引数を使用してJavaScript配列をJava配列に変換する方法を示しています。

// Create a JavaScript array
var anArray = [1, "13", false];

// Convert the JavaScript array to a Java int[] array
var javaIntArray = Java.to(anArray, "int[]");
print(javaIntArray[0]); // prints the number 1
print(javaIntArray[1]); // prints the number 13
print(javaIntArray[2]); // prints the number 0

// Convert the JavaScript array to a Java String[] array
var javaStringArray = Java.to(anArray, Java.type("java.lang.String[]"));
print(javaStringArray[0]); // prints the string "1"
print(javaStringArray[1]); // prints the string "13"
print(javaStringArray[2]); // prints the string "false"

// Convert the JavaScript array to a Java Object[] array
var javaObjectArray = Java.to(anArray);
print(javaObjectArray[0]); // prints the number 1
print(javaObjectArray[1]); // prints the string "13"
print(javaObjectArray[2]); // prints the boolean value "false"

Java配列を取得したら、Java.from()メソッドを使用してそれをJavaScript配列に変換できます。次の例は、現在のディレクトリ内にあるファイルのリストを含むJava配列を、同じ内容を持つJavaScript配列に変換する方法を示しています。

// Get the Java File type object
var File = Java.type("java.io.File");
// Create a Java array of File objects
var listCurDir = new File(".").listFiles();
// Convert the Java array to a JavaScript array
var jsList = Java.from(listCurDir);
// Print the JavaScript array
print(jsList);

注:

ほとんどの場合、Java配列をJavaScript配列に変換しなくても、スクリプトでJava配列を使用できます。

3.4 Javaインタフェースの実装

JavaScriptでJavaインタフェースを実装する構文は、Javaで匿名クラスを宣言する場合とほぼ同じです。インタフェースをインスタンス化し、同じ式でそのメソッドを(JavaScript関数として)実装します。次の例は、Runnableインタフェースの実装方法を示しています。

// Create an object that implements the Runnable interface by implementing
// the run() method as a JavaScript function
var r  = new java.lang.Runnable() {
    run: function() {
        print("running...\n");
    }
};

// The r variable can be passed to Java methods that expect an object implementing
// the java.lang.Runnable interface
var th = new java.lang.Thread(r);
th.start();
th.join();

あるメソッドがメソッドを1つだけ持つインタフェースを実装するオブジェクトを受け取る場合は、このメソッドにオブジェクトのかわりにスクリプト関数を渡すことができます。たとえば、前の例のThread()コンストラクタは、メソッドが1つだけ定義されたRunnableインタフェースを実装するオブジェクトを受け取ります。自動変換を利用して、オブジェクトのかわりにスクリプト関数をこのThread()コンストラクタに渡すことができます。次の例は、Runnableインタフェースを実装せずにThreadオブジェクトを作成する方法を示しています。

// Define a JavaScript function
function func() {
    print("I am func!");
};

// Pass the JavaScript function instead of an object that implements
// the java.lang.Runnable interface
var th = new java.lang.Thread(func);
th.start();
th.join();

サブクラスに複数のインタフェースを実装するには、関連する型オブジェクトをJava.extend()関数に渡します。詳細は、「具象Javaクラスの拡張」を参照してください。

3.5 抽象Javaクラスの拡張

抽象Javaクラスの匿名サブクラスをインスタンス化するには、そのコンストラクタに、抽象メソッドを実装する関数を値とするプロパティを持つJavaScriptオブジェクトを渡します。メソッドがオーバーロードされると、JavaScript関数はメソッドのすべてのバリアントに実装を提供します。次の例は、TimerTask抽象クラスのサブクラスをインスタンス化する方法を示しています。

var TimerTask =  Java.type("java.util.TimerTask");
var task = new TimerTask({ run: function() { print("Hello World!") } });

コンストラクタを呼び出して引数を渡すかわりに、new式の後に引数を直接指定できます。次の例は、(Javaの匿名内部クラスの定義と同じように)この構文を使って前の例の2行目を簡単にする方法を示しています。

var task = new TimerTask {
    run: function() {
        print("Hello World!")
    }
};

抽象クラスに抽象メソッドが1つだけある場合(SAMタイプ)は、コンストラクタにJavaScriptオブジェクトを渡すかわりに、そのメソッドを実装する関数を渡すことができます。次の例は、SAMタイプの使用時に構文を簡単にする方法を示しています。

var task = new TimerTask(function() { print("Hello World!") });

どちらの構文を選んでも、引数を指定してコンストラクタを呼び出す必要がある場合は、実装オブジェクトまたは関数の後に引数を指定できます。

SAMタイプを引数として取るJavaメソッドを呼び出す場合は、そのメソッドにJavaScript関数を渡すことができます。Nashornは、期待されるクラスのサブクラスをインスタンス化し、その関数を使用して唯一の抽象メソッドを実装します。次の例は、TimerTaskオブジェクトを引数として受け取るTimer.schedule()メソッドを呼び出す方法を示しています。

var Timer = Java.type("java.util.Timer");
Timer.schedule(function() { print("Hello World!") });

注:

前の構文は、期待されるSAMタイプがインタフェースであるか、またはNashornが期待されるクラスのサブクラスをインスタンス化するために使用するデフォルト・コンストラクタを持っていることを前提としています。デフォルト以外のコンストラクタは使用できません。

3.6 具象Javaクラスの拡張

あいまいさを回避するため、抽象クラスを拡張する構文では具象クラスを使用できません。具象クラスはインスタンス化できるため、このような構文は、クラスの新しいインスタンスを作成し、そのインスタンスにコンストラクタが受け取るオブジェクトを渡そうとしていると解釈される可能性があります(期待されるオブジェクト型がインタフェースである場合)。この具体例として、次の例を考えてみます。

var t = new java.lang.Thread({ run: function() { print("Thread running!") } });

このコードは、run()メソッドの指定された実装を使ってThreadクラスを拡張していると解釈することも、Runnableインタフェースを実装するオブジェクトをコンストラクタに渡すことでThreadクラスをインスタンス化している(詳細は、「Javaインタフェースの実装」を参照)と解釈することもできます。

具象Javaクラスを拡張するには、その型オブジェクトを、サブクラスの型オブジェクトを返すJava.extend()関数に渡します。次に、指定されたメソッド実装を使ってサブクラスのインスタンスを作成するために、サブクラスの型オブジェクトをJavaScriptからJavaへのアダプタとして使用します。次の例は、run()メソッドの指定された実装を使ってThreadクラスを拡張する方法を示しています。

var Thread = Java.type("java.lang.Thread");
var threadExtender = Java.extend(Thread);
var t = new threadExtender() {
    run: function() { print("Thread running!") }};

Java.extend()関数は、複数の型オブジェクトのリストを取ることができます。Javaクラスの型オブジェクトは1つしか指定できませんが、Javaインタフェースの型オブジェクトは必要な数だけ指定できます。返された型オブジェクトは、指定されたクラス(クラスを指定しなかった場合はjava.lang.Object)を拡張し、すべてのインタフェースを実装します。クラスの型オブジェクトがリストの先頭である必要はありません。

3.7 スーパークラスのメソッドへのアクセス

スーパークラスのメソッドにアクセスするには、Java.super()関数を使用します。例3-1は、java.lang.Exceptionクラスを拡張して、スーパークラスのメソッドにアクセスする方法を示しています。

例3-1 スーパークラスのメソッドへのアクセス(super.js)

var Exception = Java.type("java.lang.Exception");
var ExceptionAdapter = Java.extend(Exception);

var exception = new ExceptionAdapter("My Exception Message") {
    getMessage: function() {
        var _super_ = Java.super(exception);
        return _super_.getMessage().toUpperCase();
    }
}

try {
    throw exception;
} catch (ex) {
    print(exception);
}

例3-1のコードを実行すると、標準出力に次が出力されます。

jdk.nashorn.javaadapters.java.lang.Exception: MY EXCEPTION MESSAGE

3.8 クラスへの実装のバインド

前の項では、Javaクラスを拡張し、実装を指定するコンストラクタの追加のJavaScriptオブジェクト・パラメータを使用してインタフェースを実装する方法について説明しました。したがって、実装はクラス全体ではなく、newで作成された実際のインスタンスにバインドされます。これにはいくつかの利点があります。たとえば、Nashornは実装されるあらゆる型の組合せに対応する単一の汎用アダプタを作成できるため、実行時のメモリー・サイズの点で有利です。しかし、次の例は、異なるインスタンスが、異なるJavaScript実装オブジェクトを持つにもかかわらず、同じJavaクラスを持つことを示しています。

var Runnable = java.lang.Runnable;
var r1 = new Runnable(function() { print("I'm runnable 1!") });
var r2 = new Runnable(function() { print("I'm runnable 2!") });
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

前の例は次を出力します。

I'm runnable 1!
I'm runnable 2!
We share the same class: true

インスタンス化するクラスを外部APIに渡す場合は(たとえば、JavaFXフレームワークを使用するときは、ApplicationクラスをJavaFX APIに渡してインスタンス化します)、クラスのインスタンスではなくクラスにバインドされた実装を使ってJavaクラスを拡張するか、またはインタフェースを実装する必要があります。実装をクラスにバインドするには、実装を含むJavaScriptオブジェクトをJava.extend()関数の最後の引数として渡します。これにより、コンストラクタで追加の実装オブジェクト・パラメータが不要になるため、元のクラスと同じコンストラクタを持つクラスが作成されます。次の例は、実装をクラスにバインドする方法と、その場合は呼出しが異なれば実装クラスも異なることを示しています。

var RunnableImpl1 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") });
var RunnableImpl2 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 2!") });
var r1 = new RunnableImpl1();var r2 = new RunnableImpl2();
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

前の例は次を出力します。

I'm runnable 1!
I'm runnable 2!
We share the same class: false

実装オブジェクトをコンストラクタの呼出しからJava.extend()関数の呼出しに移動すると、コンストラクタの呼出しで追加の引数が不要になります。クラス固有の実装オブジェクトを使ってJava.extend()関数を呼び出すたびに、新しいJavaアダプタ・クラスが生成されます。クラスにバインドされた実装を持つアダプタ・クラスも、特定のインスタンスの動作をさらにオーバーライドするために、追加のコンストラクタ引数を取ることができます。このように、Java.extend()関数に渡されたクラスベースのJavaScript実装オブジェクトで実装の一部を提供する方法と、コンストラクタに渡されたオブジェクトでインスタンス用の実装を提供する方法の2つを組み合わせることができます。コンストラクタに渡されたオブジェクトで定義される関数は、クラスにバインドされたオブジェクトで定義される関数をオーバーライドします。次の例は、クラスにバインドされたオブジェクトで定義される関数をコンストラクタに渡された関数でオーバーライドする方法を示しています。

var RunnableImpl = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") });
var r1 = new RunnableImpl();
var r2 = new RunnableImpl(function() { print("I'm runnable 2!") });
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

前の例は次を出力します。

I'm runnable 1!
I'm runnable 2!
We share the same class: true

3.9 メソッド・オーバーロード・バリアントの選択

Javaメソッドは、引数の型でオーバーロードできます。Javaコンパイラ(javac)は、コンパイル時に正しいメソッド・バリアントを選択します。Nashornから呼び出されたJavaメソッドのオーバーロードの解決は、メソッドが呼び出された時点で行われます。正しいメソッド・バリアントが引数の型に基づいて自動的に選択されます。ただし、実際の引数の型に本当のあいまいさがある場合は、特定のオーバーロード・バリアントを明示的に指定できます。これにより、Nashornエンジンは呼出し時にオーバーロードの解決を行う必要がなくなるため、パフォーマンスが向上する可能性があります。

オーバーロード・バリアントは、特別なプロパティとして公開されます。これらは、メソッドの名前とそれに続くカッコで囲まれた引数の型からなる文字列の形式で参照できます。次の例は、引数としてObjectクラスを受け取るSystem.out.println()メソッドのバリアントを呼び出し、それに"hello"を渡す方法を示しています。

var out = java.lang.System.out;
out["println(Object)"]("hello");

前の例では、非修飾クラス名(Object)で正しいシグネチャが一意に識別されるため、これで十分です。シグネチャに完全修飾のクラス名を使用する必要があるのは、同一の非修飾名を持つ2つのオーバーロード・バリアントが異なるパラメータ型を使用する場合のみです(これは、異なるパッケージに同じ名前を持つパラメータ型が含まれていた場合に起こる可能性があります)。

3.10 データ型のマッピング

JavaとJavaScript間のほとんどの変換は、期待どおりに機能します。これまでの項では、JavaとJavaScript間のあまり目立たないデータ型マッピングについていくつか説明しました。たとえば、配列は明示的に変換する必要があり、JavaScript関数はJavaメソッドへのパラメータとして渡されたときにSAMタイプに自動的に変換されます。すべてのJavaScriptオブジェクトは、APIでマップを直接受け取ることができるようにjava.util.Mapインタフェースを実装しています。Java APIに数値が渡されると、その数値は期待されるターゲット数値型(boxedまたはプリミティブ)に変換されます。ただし、ターゲット型が限定的でない場合(Numberなど)は、Number型になることしか期待できず、その型がボクシングされたDoubleIntegerLongおよびその他であるかどうかを特に検査する必要があります。数値は内部の最適化によって任意のboxed型になります。また、JavaScript仕様で定義されたToNumberの変換アルゴリズムが値に適用されるため、任意のJavaScript値を、boxed型またはプリミティブ型の数値を受け取るJava APIに渡すことができます。JavaメソッドがStringまたはBooleanオブジェクトを受け取る場合は、JavaScript仕様で定義されたToStringおよびToBooleanの変換で許可されるすべての変換を使用して値が変換されます。


注意:

文字列操作の内部パフォーマンスの最適化により、JavaScript文字列がjava.lang.String型にならず、java.lang.CharSequence型になることがあります。java.lang.String引数を受け取るJavaメソッドにJavaScript文字列を渡すと、Java Stringが返されますが、メソッドのシグネチャがより汎用的である場合(たとえば、java.lang.Objectパラメータを受け取る場合)は、Java StringオブジェクトではないがCharSequenceを実装するprivateエンジン実装クラスのオブジェクトを取得できます。

目次      

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