JavaScript(Rhino)からJavaのクラスにアクセスする

以下のサイトを参照してやってみる。
Java のスクリプティング - Rhino | MDN
RhinoからJavaのクラスにアクセスするのはすごく簡単。一番簡単に使う方法は以下の様に、パッケージ名まで含めたクラスを指定して使うだけ。

js> list = new java.util.ArrayList();
[]
js> list.add(1);list.add(2);list.add(3);
true
js> list
[1.0, 2.0, 3.0]

ま、これじゃ面倒なのでインポートすることも出来る。

js> importPackage(java.util);
js> list = new ArrayList();
[]
js> list.add(1);list.add(2);list.add(3);
true
js> list
[1.0, 2.0, 3.0]

ほうほう。今までJavaScriptからJava使うってやったことなかったけど思った以上に簡単だねー。

ファイルの読み込み

js> importPackage(java.io);
br = new BufferedReader(new FileReader("/home/hironemu/text.txt"));
while(line = br.readLine()) {print(line)}
js> br.close();

こんな感じで普通にJavaのクラスを使って実現出来る。

Javaのインターフェースを実装する

例としてRunnableインターフェースを実装するのがあったので同じようにやってみる。因みにJavaでRunnableインターフェースを実装して、スレッドを実行するのは以下のようになる。

Runnable r = new Runnable() {
	@Override
	public void run() {
		System.out.println("running..");
	}
	
};
Thread t = new Thread(r);
t.start();

JavaScriptではまず、Runnableの実装をハッシュ作る↓

js> importPackage(java.lang);
js> obj = {run: function () {print("running...")}}

objはハッシュです。この場合キーには「run」、値には「function() {print("running...")}」が定義されている状態。ここがJavaScriptの面白いところで値に関数を入れることが出来る。そして、このキー呼ぶときに「()」をつけると関数として実行してくれる。

js> obj.run()
running...

こいつを、Runnableインターフェースの実装として使う。

js> r = new Runnable(obj);
adapter1@1bac748

お。このadapterってなんだろね。JavaJavaScriptの間にある何かとか?
次にThreadクラスを作るときに引数として「r」を渡してやればOK。

js> t = new Thread(r);
Thread[Thread-0,5,main]
js> t.start();
running...

これを簡略化して以下のようにもかけるらしい。Runnableみたいに実装するメソッドが一つの場合は、そのメソッドの実装だけを渡してあげればいいんだって。ThreadのコンストラクタにはRunnableを引数にとるものがあって、Runnableのメソッドは一つだけって分かってるからあとは自動でやってくれてるってことだろう。だいぶ簡単にかけるね。

js> t = Thread(function() {print("running...")});
Thread[Thread-1,5,main]
js> t.start();
running...

あり、newを書き忘れたけど実行できたね。これはなんでやろ。

Javaの配列を使う

RhinoJavaの配列を使うための構文を用意していないらしい。なので、リフレクションを使ってJavaの配列をnewする。これははじめてみたなー。

js> array = java.lang.reflect.Array.newInstance(java.lang.String, 5);
js> array.length
5
js> array[0] = "first";
first
js> array[1] = "second";
second
js> for (i in array) {print(array[i])}
first
second
null
null
null

おっと。JavaScriptのfor in構文は値を返すんじゃなくて、キーを返すみたいだね。
プリミティブな配列をつくる場合はnewInstanceの初めの引数に、TYPEを渡すらしい。(charの例はあるので、intの例を。

js> intArray = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, 2);
[I@f72617
js> intArray[0] = 1;
1
js> intArray[1] = 2;
2

この配列に無理やりStringの文字列を渡そうとすると、

js> intArray[0] = "string";
js: "<stdin>", line 66: Cannot convert string to java.lang.Integer
	at <stdin>:66

お。コンバート出来ませんってことは、コンバートしてくれそうなやつだと、、

js> intArray[0] = "10";
10

ほほう。intに変換出来る文字列だと勝手に変換してくれるんだ。べんりー。こんなのは。

js> intArray[0] = "10.02";
10.02
js> intArray[0] 
10

floatっぽい数字を入れてみると、勝手に丸めてくれる。ま、あたりまえか。

JavaScriptの文字列とJavaの文字列

JavaScriptの文字列とJavaの文字列は違う。Javaの文字列はjava.lang.Stringクラスとして定義されているて、JavaScriptの場合は、String.prototypeによって定義されている。なので、以下の様に文字の長さを知るためのメソッドにも違いがある。

js> javaString = new java.lang.String("java");
java
js> javaString.length(); // java.lang.Stringのlengthメソッド
4
js> jsString = "JavaScript";
JavaScript
js> jsString.length; // String.prototypeに定義されたプロパティlength
10

でも、この2つの違いを吸収するようにRhinoはがんばってくれてるみたい。例えば、JavaのStringを使いたいときにJavaScriptのStringを渡すことができ、上の例の「new java.lang.String("java")」としている部分ではまさにそれをやっている。"java"って文字列はJavaScriptの文字列なんだけど、これをJavaのStringに渡すときに変換してくれてるのだろう。
その他に、java.lang.Stringのクラスに定義されていないメソッドを呼び出そうとすると、JavaScriptのメソッドから探してこようとする。のかな?

js> javaString.slice(0, 2);
ja

こんな感じで、java.lang.Stringにsliceってメソッドは定義されてないんだけど、JavaScriptの方には定義されているからそっちを実行してくれるんだね。

あり。逆はダメなのね。

js> jsString.trim();
js: "<stdin>", line 97: uncaught JavaScript runtime exception: TypeError: Cannot find function trim in object JavaScript.
	at <stdin>:97