[Java, JavaScript] Oracle Nashorn: A Next-Generation JavaScript Engine for the JVM

原文はこちら。
http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html

コマンドラインツールおよびJavaアプリケーションにおける組み込みインタプリタとしてOracle Nashornを使うシナリオ

Java SE 7まで、JDKには、Mozilla RhinoベースのJavaScriptスクリプティングエンジンが同梱されていましたが、Java SE 8では、JSR 292とinvokedynamicに基づいているOracleのNashornと呼ばれる新しいエンジンが同梱されています。ECMAで規格されたJavaScriptの仕様により一層適合しており、invokedynamicでバインドされたCallsiteにより、より優れたランタイム性能を提供します。
Rhino (Mozilla Developer Network)
https://developer.mozilla.org/en-US/docs/Rhino
JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
https://jcp.org/en/jsr/detail?id=292
この記事では、Oracle Nashornを様々な方法で利用する方法をご紹介します。jjsコマンドラインツールを使ってスタンドアロンエンジンを使うだけでなく、Javaアプリケーション内の組み込みスクリプティングエンジンとしてOracle Nashornを利用する方法を取り扱います。JavaとJavaScriptの相互運用性や、両言語のシームレスな統合による、Javaのデータ型をJavaScriptから実装したり拡張したりできることをご紹介します。
このサンプルは最新のJDK 8早期アクセスリリースを使って実行できます。また、OpenJDK 8のカスタムビルドを使うこともできます。
JDK™ 8 Early Access Releases
https://jdk8.java.net/download.html
JDK 8
http://openjdk.java.net/projects/jdk8/
OpenJDKビルドインフラストラクチャのおかげで、非常に簡単にカスタムビルドを作成できます。例えば、XCodeのコマンドラインツールがインストールされていれば、Mac OS X上で以下のコマンドで作成できます。
$ sh configure && make images
[訳注]
ご存知の通り、既にJDK 8がリリースされているので、実際の動作確認はOracle JDK 8で実施しています。また、原文のコードでは動作しない部分があったため、一部修正したコードを掲載しています。

It's Just JavaScript

Oracle Nashornを使い始めるための簡単な方法は、コマンドラインからJavaScriptプログラムを実行することですが、その目的で、OracleのJDKまたはOpenJDKのビルドには、jjsと呼ばれるコマンドラインツールが含まれています。このツールは、よく知られたjavajavacjarツールと一緒に、JDKインストールディレクトリのbin/フォルダにあります。
var hello = function() {
  print("Hello Nashorn!");
};

hello();
こんな感じで評価は簡単です。
$ jjs hello.js
Hello Nashorn!
$
コード1
var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var filtered = data.filter(function(i) {
  return i % 2 == 0;
});
print(filtered);

var sumOfFiltered = filtered.reduce(function(acc, next) {
  return acc + next;
}, 0);
print(sumOfFiltered);
Oracle NashornはECMA準拠の言語実装ですので、コード1のような、もっと複雑なスニペットを実行することができます。このスニペットは、偶数だけをもとのリストから残して出力し、その偶数の合計を出力します。
2,4,6,8,10
30
Oracle Nashornは runs ECMA準拠のJavaScriptを実行しますが、Webブラウザで通常アクセス可能なオブジェクト(例えばconsolewindowなど)は利用できない、ということは知っておくべき重要なことでしょう。

Scripting Extensions

jjs -helpを実行してjjsコマンドの包括的なリストを取得すると、いくつかの興味深い機能があることにお気づきになることでしょう。
  • JavaFXアプリケーションとしてスクリプトを実行することができます。
  • JavaScriptのstrictモードを有効にすることができます。
  • Java仮想マシン(JVM)用に追加のクラスパス要素を指定することができます。
  • 興味深いスクリプティングモードを有効にすることができます。
jjsを使って(PythonやRuby、またはBashのように)JavaScriptで記述されたシステムスクリプトを実行することを考えているなら、スクリプティングモードは興味深いことでしょう。スクリプティングモードは主に2個の言語拡張(ヒアドキュメントとシェル呼び出し)で構成されています。

ヒアドキュメント

ヒアドキュメントは単なる複数行の文字列であり、BashやPerl、Rubyプログラマーにとっては慣れ親しんだ構文を使います(コード2)。テキストは<<で始まり、特別な終端マーカー(今回はEOF)が続きます。書式設定は終端マーカーまでそのまま残されています。また、JavaScriptの式を${...}の式に埋め込むことができます。このプログラムを実行すると、コード3に示すような出力が得られます。
コード2
var data = {
 foo: "bar",
 time: new Date()
};

print(<<EOF);
So...
       foo = ${data.foo}
and the current time is
       ${data.time}
EOF
コード3
$  jjs -scripting heredocs.js
So...
      foo = bar
and the current time is
      Thu Aug 01 2013 16:21:16 GMT+0200 (CEST)
$ 
ご注意いただきたいのは、スクリプトモードでは、二重引用符で囲まれた文字列が評価対象の式を埋め込むことができるということです。例えば、"Hello ${name}" はnameの値に対して評価します('Hello ${name}'は評価しません)。

シェル呼び出し

シェル呼び出しを使うと、コマンドがback-tick文字の間にあるような外部プログラムの呼び出しができます。以下の例を考えてみましょう。
var lines = 
`ls -lsa`.split("\n");
for each (var line in lines) {
  print("|> " + line);
} 
この例ではls -lsaコマンドを実行しています。シェル呼び出しは、文字列として標準コンソール出力を返しますが、その中で行を分割し、先頭に|">"を追加して出力しています(コード4)。呼び出されたプロセスをより細かく、精巧な制御をする必要がある場合は、$EXEC関数があることを知っておくべきでしょう。この関数は標準入力、出力、およびエラーストリームへのアクセスを提供します。
コード4
jjs -scripting dir.js
|> total 72
|>  0 drwxr-xr-x  2 jponge  staff    238 Aug  1 16:12 .
|>  0 drwxr-xr-x  5 jponge  staff    170 Aug  1 12:15 ..
|>  8 -rw-r--r--  1 jponge  staff     90 Jul 31 23:36 dir.js
|>  8 -rw-r--r--  1 jponge  staff    304 Aug  1 15:56 hello.js
|>  8 -rw-r--r--  1 jponge  staff    143 Aug  1 16:12 heredocs.js
|>
$ 

その他

スクリプティングモードではその他にもいろいろな機能があります。
  • $ENV変数はシェルの環境変数を提供します。
  • $ARG変数はプログラムのコマンドライン引数配列です。
  • コメントは#で開始できます。UNIX系システムでスクリプトを実行可能にする上で有用です。exit(code)quit()の両関数で現在のJVMプロセスを終了することができます。 
次のexecutable.jsファイルをみてみましょう。
#!/usr/bin/env jjs -scripting
print(
"Arguments (${$ARG.length})");
for each (arg in $ARG) {
  print("- ${arg}")
} 
このJavaScriptファイルを実行可能にして、呼び出すことができます(引数は--の後に続きます)。(コード5)。
コード5
$ chmod +x executable.js
$ ./executable.js
Arguments (0)
$ ./executable.js -- hello world !
Arguments (3)
- hello
- world
- !
$ 

Embedding Oracle Nashorn

OracleのNashornを埋め込む公開APIは単純にjavax.scriptです。Oracle Nashornが使用可能であれば、そのスクリプティングエンジンはnashorn識別子を通じてアクセスできます。
コード6では、Oracle NashornにJavaアプリケーションからアクセスする方法を示しています。この中で、Nashornを使いsum関数を定義し、呼び出し、その結果を表示する関数を作成しています。
コード6
package sample1;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class Hello {

  public static void main(String... args) throws Throwable {
    ScriptEngineManager engineManager = new ScriptEngineManager();
    ScriptEngine engine = 
engineManager.getEngineByName("nashorn");
    engine.eval("function sum(a, b) { return a + b; }");
    System.out.println(engine.eval("sum(1, 2);"));
  }
}
このスクリプトエンジンオブジェクトは、Oracle Nashornインタプリタの唯一のエントリポイントですが、javax.script.Invocableインターフェイスにキャストすることもできます。
Invocable invocable = (
Invocable) engine;
System.out.println(
invocable.invokeFunction(
"sum", 10, 2)); 
Invocableインターフェースは、評価対象のコードをJavaインターフェースのリファレンスに変換するメソッドも提供します。 以下のように、Adderというインターフェースが存在すると仮定しましょう。
public interface Adder {
  int sum(int a, int b);
} 
評価対象のコードは2個の引数を伴うsum関数を定義しているため、実装として以下のように利用することができます。
Adder adder = 
invocable.getInterface(
  Adder.class);
System.out.println(
  adder.sum(2, 3));  
これはJavaScriptからJavaの型を拡張する便利な方法ですが、幸いにも次章でご覧戴くように、これが唯一の方法ではありません。
必ずしもすべてのJavaScriptコードは、String: java.io.Readerから評価されるわけではありません。インスタンスをつかうことも可能です(コード7)。
コード7
engine.eval(new FileReader("src/sample1/greeter.js"));
System.out.println(invocable.invokeFunction("greet", "Julien"));
スコープやスクリプティングエンジンのバインディングを定義する方法に関する情報を含めて、詳細情報はjavax.scriptのAPIを確認すべきです。

mustache.js

それでは、Javaアプリケーションから実際にJavaScriptライブラリを呼ぶことにしましょう。そのために、mustache.jsテンプレート・ライブラリを使ってみましょう。これはHTMLアプリケーションのビュー・フラグメントをレンダリングするために使用する、人気のあるテンプレートライブラリです。簡単に言えば、JSONデータオブジェクト{"name":"Bean"}とテンプレート"Hello {{name}}"を与えると、Mustacheは"Hello Bean"と描画します。とはいえ、条件、コレクションの反復などをサポートしているので、テンプレートエンジンは、それ以上のことができるのですがね。
mustache.jsをダウンロードしたと仮定しましょう。コード8はJavaとの統合例です。
mustache.js - Logic-less {{mustache}} templates with JavaScript
https://github.com/janl/mustache.js
コード8
package sample2;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;

public class Mustache {

  public static void main(String... args) throws Throwable {
    ScriptEngineManager engineManager = new ScriptEngineManager();
    ScriptEngine engine = engineManager.getEngineByName("nashorn");
    engine.eval(new FileReader("src/sample2/mustache.js"));
    Invocable invocable = (Invocable) engine;

    String template = "Email addresses of {{contact.name}}:\n" +
        "{{#contact.emails}}\n" +
        "- {{.}}\n" +
        "{{/contact.emails}}";

    String contactJson = "{" +
        "\"contact\": {" +
        "\"name\": \"Mr A\", \"emails\": [" +
        "\"contact@some.tld\", \"sales@some.tld\"" +
        "]}}";
    Object json = engine.eval("JSON");
    Object data = invocable.invokeMethod(json, "parse", contactJson);

    Object mustache = engine.eval("Mustache");
    System.out.println(invocable.invokeMethod(
mustache, "render", template, data));
  }
} 
Oracle Nashorn用のスクリプティングエンジンへの参照を取得した後、mustache.jsコードを評価します。その後、StringとしてMustacheテンプレートを定義します。データモデルは、JSONオブジェクトである必要があります。今回の場合、まず、それを文字列として定義し、JSON.parseを呼び出してJSONオブジェクトにした上で、Mustache.renderを呼び出すことができます。このプログラムを実行すると、以下のような出力を得ることができます。テンプレートのレンダリングのためにmustache.jsを呼び出しています。
$ java sample2.Mustache 
Email addresses of Mr A:
- contact@some.tld
- sales@some.tld

$ 

Java Seen from Oracle Nashorn

たいていの場合、Java APIをOracle Nashornから呼び出すのが簡単でしょう。結果としてのコードはJavaScript内で書かれたJavaです。
コード9
print(java.lang.System.currentTimeMillis());

基本的な例

System.currentTimeMillis() という静的メソッドを呼び出すことができます(コード9)。Javaオブジェクトをnewでインスタンス化できます。
var file = 
new java.io.File("sample.js");
print(file.getAbsolutePath());
print(file.absolutePath); 
java.io.FileabsolutePathメソッドやパブリックフィールドを定義していませんが、Oracle Nashornはそのためのプロパティを推測するため、file.absolutePathという表現はfile.getAbsolutePath()と同等であることに注意してください。実のところ、Oracle NashornはgetXY()setXY(value)メソッドをプロパティとして扱います。


配列を取り扱う

次のスニペットではjava.util.LinkedListのインスタンスとしてキューを取り込みます。
var stack = 
new java.util.LinkedList();
[1, 2, 3, 4].forEach(function(item) {
  stack.push(item);
});

print(stack);
print(stack.getClass()); 
このコードでは次のような出力を得ます。我々が直接JavaScriptからJavaオブジェクトを操作していることを確認できます。
[4, 3, 2, 1]
class java.util.LinkedList 
また、この場合では最も効率的な方法ではありませんが、コレクションをソートするために、新しいJava8のstream APIを使ってコレクションのソートすることもできます。
var sorted = stack
  .stream()
  .sorted()
  .toArray();
print(sorted); 
今度は[Ljava.lang.Object;@473b46c3のような感じで出力されますが、これはJavaネイティブ配列を示しています。しかし、Java配列はJavaScript配列とは異なります。内部的には、Oracle Nashornはjava.util.Mapを実装するカスタムクラスを使ってJavaScript配列を提供しています。Javaオブジェクトが提供するOracle Nashornのtoメソッドとfromメソッドを使い、変換を実行することができます。
var jsArray = Java.from(sorted);
print(jsArray);

var javaArray = 
Java.to(jsArray);
print(javaArray); 
結果は以下のような感じです。
1,2,3,4
[Ljava.lang.Object;@23a5fd2

クラスのインポート

デフォルトでは、Javaの型への参照はすべて、完全修飾する必要があります(例えば、java.lang.Stringjava.util.LinkedHashSetなど)。Oracle Nashornは、デフォルトではjavaパッケージをインポートしません。それは、StringObjectへの参照がJavaScriptにおける対応する型と競合するためです。そのため、Javaの文字列は、Stringではなくjava.lang.Stringです。
Mozilla Rhinoは、Oracle Nashorn登場前にOracle JDKのリリースで提供していたJavaScriptエンジンの実装でした。サードパーティのJavaScriptファイルをロードする、load(path)関数が特徴でしたが、まだOracle Nashornにもありますので、これを使うと、(Javaでの明示的なインポートのように)クラスをインポートするためのimportClassや、(Javaでのワイルドカード・インポートのように)パッケージをインポートするimportPackageを提供する特別な互換性モジュールをロードすることができます。
load(
"nashorn:mozilla_compat.js");

importClass(java.util.HashSet);
var set = new HashSet();

importPackage(java.util);
var list = new ArrayList(); 
こうした関数は、シンボリックリファレンスを、解釈対象のJavaScriptのコードのグローバルスコープにインポートするということを覚えておきましょう。これらの関数はまだ互換性のためにサポートされていますが、mozilla_compat.jsimportClassの使用は推奨されません。代わりに、JavaImporterという、Mozilla Rhinoの遺産から生まれた別の関数を使うことが推奨されています(コード10)。
コード10
var CollectionsAndFiles = new JavaImporter(
    java.util,
    java.io,
    java.nio);

with (CollectionsAndFiles) {
  var files = new LinkedHashSet();
  files.add(new File("Plop"));
  files.add(new File("Foo"));
  files.add(new File("w00t.js"));
} 
JavaImporterは、Javaパッケージとして可変引数を取り、指定したパッケージのインポートが含まれるスコープを持つ文を使って、返却されたオブジェクトを使うことができます。グローバルJavaScriptスコープは影響を受けません。JavaImporterimportClassimportPackageのずっとよい代替手段です。


オーバーロードされたメソッド

Javaは、メソッドのオーバーロード、つまり、一つのクラス内に同じ名前で異なるシグネチャを持ついくつかのメソッドを定義することができます。 java.io.PrintStreamクラスがいい例で、オブジェクト、文字列、配列、およびプリミティブ型のための多くのprintメソッドやprintlnメソッドのprintlnメソッドを提供しています。
Oracle Nashornは呼び出し毎に実行時に適切なターゲットを選択します。これはつまり、Java APIを使う際にオーバーロードされたメソッドを心配する必要がない、ということです。それでも、必要であれば、ターゲットを正確に選択する方法があります。こうした要求は次のような条件で曖昧なパラメータを使っているときによく発生します。それは、異なるインターフェース型をオーバーロードされたメソッドが許容している(例えばjava.util.concurrentのExecutorServiceのsubmitメソッドを使っている)関数オブジェクトを渡している場合によく起こります。
次のコードでは、printlnの最初の呼び出しは、println(String)のオーバーロードされたメソッドを選択します。2回目の呼び出しではJavaScriptのobjectプロパティを使ってprintln(Object)バリアントにアクセスします。渡される文字列は、Oracle Nashornが解決時に使用している署名を提供します。注意いただきたいのは、例外的に、 Javaパッケージにあるクラスは、修飾されている必要はありません。したがって、有効だけど長くなるprintln(java.lang.Object)の代わりに、println(Object)と書くことができます。
var stdout = 
java.lang.System.out;
stdout.println("Hello");
stdout["println(Object)"]( 
"Hello");

Typeオブジェクト

Java.type 関数を使って、正確なJavaの型への参照を取得することができます。これらにはオブジェクトだけでなくプリミティブ型や配列も含まれます。
var LinkedList = Java.type(
"java.util.LinkedList");
var primitiveInt = Java.type(
"int");
var arrayOfInts = Java.type(
"int[]"); 
返されるオブジェクトは、Javaの型へのマッピングのOracle Nashorn固有表現です。これらはjava.lang.Classのインスタンスとは異なるので注意が必要です。Typeオブジェクトはコンストラクタとして、そしてinstanceofベースの比較において便利です。コード11を見てみましょう。
コード11
var list = new LinkedList;
list.add(1);
list.add(2);
print(list);
print(list instanceof LinkedList);

var a = new arrayOfInts(3);
print(a.length);
print(a instanceof arrayOfInts);
TypeオブジェクトとJavaクラスの参照の間を行き来できます。Typeオブジェクトのclassプロパティは、java.lang.Classにおける同等のものを返します。同様に、対応する型オブジェクトを取得すると、staticプロパティをjava.lang.Classインスタンスで利用できるようになります。
コード12
print(LinkedList.class);
print(list.getClass().static);
print(LinkedList.class === list.getClass());
print(list.getClass().static === LinkedList);
コード12のコードでは、以下のような出力を得ます。
class java.util.LinkedList
[JavaClass java.util.LinkedList]
true
true 

Extending Java Types

Oracle NashornではJavaの型をJavaScriptから拡張するためのシンプルなメカニズムを提供しています。インターフェース実装と具象サブクラスを提供できることは重要です。

インターフェースの実装

Javaインタフェースを考えた場合、実装を提供するための簡単な方法は、インスタンス化し、コンストラクタ関数に実装対象のメソッドがプロパティとして与えられているJavaScriptオブジェクトを渡すことです。
コード13では、nextメソッドとhasNextメソッドの実装を与えて、java.util.Iteratorの具体的な実装を提供しています(removeメソッドはJava8のデフォルトメソッドが提供しています)。これを実行し、期待通りに動作するか確認できます(コード14)。
コード13
var iterator = new java.util.Iterator({
  i: 0,
  hasNext: function() {
    return this.i < 10;
  },
  next: function() {
    return this.i++;
  }
});

print(iterator instanceof Java.type("java.util.Iterator"));
while (iterator.hasNext()) {
  print("-> " + iterator.next());
} 
コード14
true
-> 0
-> 1
-> 2
-> 3
-> 4
-> 5
-> 6
-> 7
-> 8
-> 9 
インターフェイスが単一のメソッドで構成されている場合に、関数オブジェクトを直接渡すことができます。その際、明示的にnew演算子を呼び出す必要はありません。コード15の例では、collection streamsで説明しています。
コード15
var list = java.util.Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
var odd = list.stream().filter(function(i) {
  return i % 2 == 0;
});
odd.forEach(function(i) {
  print(">>> " + i);
}); 
コード15のコードを実行すると、以下のような結果が得られます。
>>> 2
>>> 4
>>> 6
>>> 8 
Oracle Nashornは、Oracle Nashorn関数の形で言語拡張することもできる点は注目すべきでしょう。つまり、小さなラムダ関数のために簡略化した構文を提供します。これは、Java APIから単一の抽象メソッド型を期待されているところでもどこでも使えます。そのため、コード15の以下のコードを
var odd = list.stream().filter(
  function(i) {
  return i % 2 == 0;
}); 
このように書き換えることができます。
var odd = list.stream().filter(
  function(i) i % 2 == 0);
この言語拡張はラムダ式をサポートする新しいJava SE 8 APIを扱う際に便利です。その理由は、Javaのラムダ式を期待される箇所のどこででもJavaScriptの関数を使うことができるからです。また、このより短縮形をJavaScript 1.8エンジンがサポートすることにもご注意下さい。
抽象クラスの場合はインターフェースと同じです。つまり、必要なメソッド実装を伴うJavaScriptオブジェクトをそのコンストラクタ関数に提供します。または、単一の抽象メソッドクラスのインスタンスを必要とする場合には関数を直接渡します。

インスタンスバインドされた実装の使用

具象クラスを拡張するために、Java.extend関数を使う必要があります。第1引数として拡張対象の基本クラスを意味する型オブジェクトをとります。パラメータがインターフェース型の場合、ベースクラスはjava.lang.Objectに仮定します。追加のパラメータとして他のタイプを与えることで、実装されたインターフェースのセットを指定することができます。
コード16の例を考えてみましょう。Java.extend関数は、extenderと呼ばれるTypeオブジェクトを返します。これを呼び出して、具象サブクラスを作成することができます。今回の場合、instancejava.lang.Objectのサブクラスであり、java.lang.Comparablejava.io.Serializable)の2個のインターフェースを実装しています。この実装を、コンストラクタに渡されたJavaScriptオブジェクトを介して作成されたインスタンスに渡します。
コード16
var ObjectType = Java.type("java.lang.Object");
var Comparable = Java.type("java.lang.Comparable");
var Serializable = Java.type("java.io.Serializable");

var MyExtender = Java.extend(
ObjectType, Comparable, Serializable);
var instance = new MyExtender({
  someInt: 0,
  compareTo: function(other) {
    var value = other["someInt"];
    if (value === undefined) {
      return 1;
    }
    if (this.someInt < value) {
      return -1;
    } else if (this.someInt == value) {
      return 0;
    } else {
      return 1;
    }
  }
});

print(instance instanceof Comparable);
print(instance instanceof Serializable);
print(instance.compareTo({ someInt: 10 }));
print(instance.compareTo({ someInt: 0 }));
print(instance.compareTo({ someInt: -10 }));
コード16のコードを実行すると、以下のようにコンソールに出力されます。
true
true
-1
0
1 

クラスバインドされた実装の使用

実装はインスタンス毎に違いますが、同じextenderタイプから作成したインスタンスは同じクラスを共有しています(コード17)。
コード17
var anotherInstance = new MyExtender({
  compareTo: function(other) {
    return -1;
  }
});

// Prints 'true'!
print(instance.getClass() === anotherInstance.getClass());
これは多くの場合問題ありませんが、各インスタンスに実装を渡すのは、必ずしも便利とは限りません。実際、依存性注入のAPIに見られるようなオブジェクトをそのような制御の反転メカニズム機構の形を通じてインスタンス化する必要がある場合も存在します。このような場合には、3rdパーティのAPIは通常、以前の実装クラスへの参照が必要であり、先ほどのextenderメカニズムは不適切です。
幸いなことに、Java.extendを使い、実装をクラス定義にバインドすることができます(各インスタンスに対して実装を指定する必要はありません)。そのためには、最後のパラメータとしてJavaScriptの実装オブジェクトを渡して挙げればよいのです。

コード18の場合、FooCallableBarCallableという2個のextenderタイプを定義しています。foobarのインスタンスを作成する際には、実装を渡す必要はありません。インスタンスに同じクラス定義がないことを確認することもできます。実は、FooCallable.classBarCallable.classjava.lang.Class定義のインスタンスが必要な3rdパーティーJava APIに渡すことができます。
コード18
var Callable = Java.type("java.util.concurrent.Callable");

var FooCallable = Java.extend(Callable, {
  call: function() {
    return "Foo";
  }
});

var BarCallable = Java.extend(Callable, {
  call: function() {
    return "Bar";
  }
});

var foo = new FooCallable();
var bar = new BarCallable();

// 'false'
print(foo.getClass() === bar.getClass());

print(foo.call());
print(bar.call());
この例では説明されていませんが、クラスバインドされた実装で定義されたクラスはそのスーパークラスから継承したコンストラクタを提供します。この例では、オブジェクトが暗黙的にjava.lang.Objectを拡張し、java.util.concurrent.Callableを実装しています。そのため、対応するクラス定義は引数を伴わないパブリックなコンストラクタを有します。

インスタンスバインドされた実装とクラスバインドされた実装の利用

大事なことを言い忘れていましたが、インスタンスバインドされた実装とクラスバインドされた実装を組み合わせることができます。実装オブジェクトをコンストラクタに渡すことで、メソッドの全てもしくは一部クラスバインドされた実装を改善することができます(コード19)。
コード19
var foobar = new FooCallable({
  call: function() {
    return “FooBar”;
  }
});

// 'FooBar'
print(foobar.call());

// 'true'
print(foo.getClass() === foobar.getClass());  

Conclusion

この記事では、コマンドラインツールとして、そしてJavaアプリケーションに埋め込まれたインタプリタとしてOracle Nashornを使用するための様々なシナリオを説明しました。また、JavaScriptからJavaの型を実装して拡張する機能など、JavaとJavaScriptの相互運用性についても取り扱いました。

Oracle Nashornは、JVM上の多言語アプリケーションのためのスクリプト言語を活用するための優れた方法です。JavaScriptは人気のある言語であり、幅広いユースケースでJavaとJavaScriptの間の相互作用はシームレスで簡単です。

著者について

Julien Ponge (@jponge)は、現在INSA de Lyonでコンピュータ理工学の准教授であり、長年オープンソースに携わってきた、いわば職人です。彼はCITI研究室の活動の一環として、プログラミング言語、仮想マシン、およびミドルウェアを主たる研究分野としています。
Département Télécommunications - INSA de Lyon
https://telecom.insa-lyon.fr/

0 件のコメント:

コメントを投稿