ナショーン入門

Nashornの概要

1. 前書き

この記事では、Java 8以降のJVMの新しいデフォルトJavaScriptエンジンのNashorn –に焦点を当てています。

多くの高度な手法を使用して、Rhino,と呼ばれる前のバージョンよりもNashornのパフォーマンスを桁違いに向上させているため、変更する価値があります。

それを使用できるいくつかの方法を見てみましょう。

2. コマンドライン

JDK 1.8には、JavaScriptファイルを実行するために使用できるjjsと呼ばれるコマンドラインインタープリターが含まれています。引数なしで開始した場合は、REPL(対話型シェル)として使用できます。

$ $JAVA_HOME/bin/jjs hello.js
Hello World

ここで、ファイルhello.jsには単一の命令が含まれています:print(“Hello World”);

同じコードをインタラクティブな方法で実行できます。

$ $JAVA_HOME/bin/jjs
jjs> print("Hello World")
Hello World

最初の行として#!$JAVA_HOME/bin/jjsを追加することにより、ターゲットスクリプトの実行にjjsを使用するように* nixランタイムに指示することもできます。

#!$JAVA_HOME/bin/jjs
var greeting = "Hello World";
print(greeting);

そして、ファイルは通常どおり実行できます。

$ ./hello.js
Hello World

3. 組み込みスクリプトエンジン

JVM内からJavaScriptを実行する2番目の、おそらくより一般的な方法は、ScriptEngine.を使用することです。JSR-223はスクリプトAPIのセットを定義し、任意の動的言語に使用できるプラグ可能なスクリプトエンジンアーキテクチャを可能にします(もちろん、JVM実装があります)。

JavaScriptエンジンを作成しましょう:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

Object result = engine.eval(
   "var greeting='hello world';" +
   "print(greeting);" +
   "greeting");

ここでは、新しいScriptEngineManagerを作成し、すぐにnashornという名前のScriptEngineを指定するように要求します。 次に、いくつかの命令を渡して、予想どおり、String "hello world"である結果を取得します。

4. スクリプトへのデータの受け渡し

Bindingsオブジェクトを定義し、それを2番目のパラメーターとしてeval関数に渡すことにより、データをエンジンに渡すことができます。

Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "example");

String script = "var greeting='Hello ';" +
  "for(var i=count;i>0;i--) { " +
  "greeting+=name + ' '" +
  "}" +
  "greeting";

Object bindingsResult = engine.eval(script, bindings);

このスニペットを実行すると、「Hello example example example」が生成されます。

5. JavaScript関数の呼び出し

もちろん、JavaコードからJavaScript関数を呼び出すことは可能です。

engine.eval("function composeGreeting(name) {" +
  "return 'Hello ' + name" +
  "}");
Invocable invocable = (Invocable) engine;

Object funcResult = invocable.invokeFunction("composeGreeting", "example");

これにより、「Hello example」が返されます。

6. Javaオブジェクトの使用

JVMで実行しているため、JavaScriptコード内からネイティブJavaオブジェクトを使用できます。

これは、Javaオブジェクトを使用して実行されます。

Object map = engine.eval("var HashMap = Java.type('java.util.HashMap');" +
  "var map = new HashMap();" +
  "map.put('hello', 'world');" +
  "map");

7. 言語拡張

Nashorn is targeting ECMAScript 5.1ですが、JavaScriptの使用法を少し良くするための拡張機能を提供します。

7.1. For-Eachを使用したコレクションの反復

For-eachは、さまざまなコレクションの反復を簡単にするための便利な拡張機能です。

String script = "var list = [1, 2, 3, 4, 5];" +
  "var result = '';" +
  "for each (var i in list) {" +
  "result+=i+'-';" +
  "};" +
  "print(result);";

engine.eval(script);

ここでは、for-each反復構造を使用して配列の要素を結合します。

結果の出力は1-2-3-4-5-になります。

7.2. 関数リテラル

単純な関数宣言では、中括弧を省略できます。

function increment(in) ++in

明らかに、これは単純な1行関数に対してのみ実行できます。

7.3. 条件付きキャッチ条項

指定された条件が真の場合にのみ実行される保護されたcatch句を追加することができます。

try {
    throw "BOOM";
} catch(e if typeof e === 'string') {
    print("String thrown: " + e);
} catch(e) {
    print("this shouldn't happen!");
}

これにより、「String thrown: BOOM」が出力されます。

7.4. 型付き配列と型変換

Java型付き配列を使用して、JavaScript配列との間で変換を行うことができます。

function arrays(arr) {
    var javaIntArray = Java.to(arr, "int[]");
    print(javaIntArray[0]);
    print(javaIntArray[1]);
    print(javaIntArray[2]);
}

Nashornはここでいくつかの型変換を実行して、動的に型指定されたJavaScript配列のすべての値が整数のみのJava配列に収まるようにします。

上記の関数を引数[100, “1654”, true]で呼び出した結果、100、1654、および1(すべての数値)が出力されます。

Stringとブール値は、対応する論理整数に暗黙的に変換されました。

7.5. Object.setPrototypeOfを使用したオブジェクトのプロトタイプの設定

Nashornは、オブジェクトのプロトタイプを変更できるようにするAPI拡張機能を定義します。

Object.setPrototypeOf(obj, newProto)

この関数は通常、Object.prototype.protoの代替として優れていると考えられているため、すべての新しいコードでオブジェクトのプロトタイプを設定するための推奨される方法です。

7.6. 魔法のnoSuchPropertynoSuchMethod

undefinedプロパティがアクセスされるか、undefinedメソッドが呼び出されるたびに呼び出される、オブジェクトのメソッドを定義することができます。

var demo = {
    __noSuchProperty__: function (propName) {
        print("Accessed non-existing property: " + propName);
    },

    __noSuchMethod__: function (methodName) {
        print("Invoked non-existing method: " + methodName);
    }
};

demo.doesNotExist;
demo.callNonExistingMethod()

これは印刷されます:

Accessed non-existing property: doesNotExist
Invoked non-existing method: callNonExistingMethod

7.7. オブジェクトプロパティをObject.bindPropertiesでバインドする

Object.bindPropertiesを使用して、あるオブジェクトから別のオブジェクトにプロパティをバインドできます。

var first = {
    name: "Whiskey",
    age: 5
};

var second = {
    volume: 100
};

Object.bindProperties(first, second);

print(first.volume);

second.volume = 1000;
print(first.volume);

これが作成するのは「ライブ」バインディングであり、ソースオブジェクトへの更新はバインディングターゲットを通しても表示されることに注意してください。

7.8. 場所

現在のファイル名、ディレクトリ、および行は、グローバル変数FILE, DIR, LINE:から取得できます。

print(__FILE__, __LINE__, __DIR__)

7.9. String.prototypeの拡張

NashornStringプロトタイプで提供する、2つの単純ですが、非常に便利な拡張機能があります。 これらはtrimRightおよびtrimLeft関数であり、当然のことながら、空白が削除されたStringのコピーを返します。

print("   hello world".trimLeft());
print("hello world     ".trimRight());

先頭または末尾のスペースなしで「hello world」を2回印刷します。

7.10. Java.asJSONCompatible関数

この関数を使用すると、Java JSONライブラリの期待と互換性のあるオブジェクトを取得できます。

つまり、それ自体、またはそれを介して推移的に到達可能なオブジェクトがJavaScript配列である場合、そのようなオブジェクトは、配列要素を公開するためのListインターフェイスも実装するJSObjectとして公開されます。

Object obj = engine.eval("Java.asJSONCompatible(
  { number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })");
Map map = (Map)obj;

System.out.println(map.get("greet"));
System.out.println(map.get("primes"));
System.out.println(List.class.isAssignableFrom(map.get("primes").getClass()));

これにより、「hello」、[2, 3, 5, 7, 11, 13]true.の順に出力されます。

8. スクリプトをロードする

ScriptEngine内から別のJavaScriptファイルをロードすることもできます。

load('classpath:script.js')

スクリプトは、URLからロードすることもできます。

load('/script.js')

JavaScriptには名前空間の概念がないため、すべてがグローバルスコープに組み込まれることに注意してください。 これにより、ロードされたスクリプトがコードまたは相互に名前の競合を引き起こす可能性があります。 これは、loadWithNewGlobal関数を使用して軽減できます。

var math = loadWithNewGlobal('classpath:math_module.js')
math.increment(5);

次のmath_module.jsの場合:

var math = {
    increment: function(num) {
        return ++num;
    }
};

math;bai

ここでは、increment.という単一の関数を持つmathという名前のオブジェクトを定義しています。このパラダイムを使用して、基本的なモジュール性をエミュレートすることもできます。

8. 結論

この記事では、Nashorn JavaScriptエンジンのいくつかの機能について説明しました。 ここで紹介する例では、文字列リテラルスクリプトを使用しましたが、実際のシナリオでは、スクリプトを別々のファイルに保持し、Readerクラスを使用してロードすることをお勧めします。

いつものように、この記事のコードはすべて利用可能なover on Githubです。