java使いのためのScala の勉強のタメの資料作り javaと連携

世の中のjava脳な奴らのためにちょっと調査してやるか。

javaとどうよ

http://d.hatena.ne.jp/nazoking/20100513/1273688372 の続き

とりあえずjavaのインターフェースを実装できないとこっそりjavaの中に入り込めないよね。

import java.lang.Runnable;
public class Fuga {
	public static void main(String[] args) {
		Runnable n = new Hoge();
		n.run();
	}
}

ってな時に、Hogeクラスをscalaで実装するにはどうしたらいいのか?

class Hoge extends AnyRef with java.lang.Runnable{
	def run():Unit ={
		println("running!");
	}
}

でした。javaだとextends Object とかはいらないので長く迷ったがscalaだと「 extends AnyRef 」は必要みたい。
つうかHogeとかjava.lang.Runnableとかすっごいさりげなく連携してる。これならさらっとその部分だけscalaで書いてコミットしても見つからない!(scalaプラグインインストールしてないメンバーがチェックアウトすればばれます)
AnyRef はなんかすべてのクラスの大本になる物らしい……ようはObjectクラスなんじゃないの?

AnyRefって?

http://www.jarvana.com/jarvana/view/org/scala-lang/scala-library/2.7.3/scala-library-2.7.3-javadoc.jar!/scala/AnyRef.html
によるとなんかいろいろ定義されているっぽい。これってJavaのクラスをscalaに持ってきても使えるのか?

「AnyRef Object AnyVal」でググるとそれっぽい記事を発見
http://itpro.nikkeibp.co.jp/article/COLUMN/20090106/322252/
Javaのプリミティブ型(intとかオブジェクトじゃない奴)の元になってるのがAnyVal、Javaの普通のクラス(java.lang.Object)に対応するのがAnyRef、その二つの親にAnyがあるらしい。

ふむじゃあこんなコードが書けるのかな?

import java.lang.Runnable;
public class Fuga {
	public static void main(String[] args) {
		Hoge n = new Hoge();
		Object a = new Object();
		Object b = n.run(a);
	}
}
class Hoge extends AnyRef with java.lang.Runnable{
	def run(obj:AnyRef):AnyRef ={
		return obj;
	}
}

お、なんか通った……ラップされて帰ってきてるのかな?ってこと出もうちょっと詳しく見てみる

import junit.framework.Assert;
import org.junit.Test;

public class Fuga {
	@Test
	public void main() {
		Hoge n = new Hoge();
		Object a = new Object();
		Assert.assertEquals(a, n.run(a));
		Assert.assertEquals(a.hashCode(), n.run(a).hashCode());
		Assert.assertTrue(a==n.run(a));
		Assert.assertEquals(n.run(a).getClass(),Object.class);
	}
}

わお テスト通る(junit4を入れました)。ラップされているわけではないようだ。多分だけど、AnyRefに生えてるいろんなメソッドはコンパイラが勝手にあると考えて適用するのではないか。このへんはimplicitなんとかを勉強とかするとそういう機能が実装できることが分かるはず。とりあえずObjectは変にラップされたりせずにjavaの世界に帰ってくる。
じゃあ逆はどうだろう。scalaの世界で作られたオブジェクトをjavaに持ち込んだとき、AnyRef特有のメソッド(neとか)はあるのだろうか?

public class Fuga {
	public void main() {
		new Hoge().ne("");
	}
}

エクリプス様に怒られてコンパイルできない……ちなみにここでctrl+1 を押して「Create method ..」をやったらHoge.scalaのファイルが開いたけどスタブは作ってくれませんでした。

そういえばscalaって演算子オーバーロード出来るんだよね

class Hoge{
	var a:Int=1;
	def ^(obj:Int):Unit ={
		a += obj;
	}
	def add(obj:Int):Unit ={
		this ^ obj // 上で定義したのをつかってる
	}
}

こんなんできる。
じゃあこういうのをjavaから呼び出すには?……わからん……と思ったけどエクリプス先生が補完してくれたYo!

public class Fuga {
	public void main() {
		Hoge hoge = new Hoge();
		hoge.$up(1);
	}
}

ふむ、なんか適当な単語が$つきで定義されてるのかな。とりあえずエクリプス先生がいらっしゃればその演算子っぽいメソッド使えねーみたいなこともないわけだ(わかりにくいけどなー)

プロパティーの可視性

ところで上のに次の行を入れるとエラーになる。

		System.out.println(hoge.a);

The field Hoge.a is not visible」あれーscalaのプロパティーって全部publicじゃなかったのかメソッドだけか。例によってctrl+1しても補完してくれないし。
試しにscalaでFugaを作ってみる

object FugaScala {
	 def main(args: Array[String]): Unit = {
		val hoge = new Hoge();
		hoge ^ 1;
		println(hoge.a);
	}
}

ふむ、もんだいないなぁと思っていろいろしてたらこんなコードが通った

public class Fuga {
	public static void main(String[] args) {
		Hoge hoge = new Hoge();
		hoge.$up(1);
		System.out.println(hoge.a());
	}
}

なんと! 同名のメソッド! えーセッターは?
エクリプスでクラスファイルを開いてバイトコードからインターフェースっぽい物を見ると

class Hoge{
  private int a;
  public int a();
  public void a_$eq(int arg0); // 多分 a= のつもりなんだと思われ
  public void $up(int obj);
  public void add(int obj);
}

こんな感じだった。つまり

		Hoge hoge = new Hoge();
		hoge.a_$eq(10);
		System.out.println(hoge.a());

とするとhoge.a に10が入るわけだ。
じゃあ逆が気になるよね……

public class Fuga {
	String a="プロパティー";
	public int a(){
		return "メソッド";
	}
}

とすると……

object Hoge{
	def main(args: Array[String]): Unit = {
		val fuga=new Fuga;
		println(fuga.a);
		println(fuga.a());
	}
}

なるほど、プロパティー、メソッド の順で呼び出された。微妙に間違えやすそうだが、まあ問題ない。ちなみに

object Hoge{
	var a:String="プロパティー";
	def a:String="メソッド";
}

は、「method a is defined twice」でエラー。

public class Fuga {
	public String a(){
		return "メソッド";
	}
}

object Hoge{
  def main(args: Array[String]): Unit = {
    println((new Fuga).a)
  }
}

と呼び出せるし、

public class Fuga {
	public String a="プロパティー";
}

も全く同じHogeで呼び出せる。が、コードの編集・保存を何度か繰り返さないとエクリプスさんが自動再コンパイルしてくれなくってわけのわからんエラーに……。これが型推論というかコンパイラマジックの微妙さなのかエクリプスプラグインの不出来なのか。

まとめ

うん、なんかだいたい分かった気がする! javaと連携させようと思ったらビルド環境さえ何とかすればこっそり使ってもばれないくらい(他人のコミットしたそーすとか誰も見てないから!)。

ちょっと眠いので今日はここまで。次からはちょっとだけでもScalaにするとすごい助かる記事が書けるはず……ビルド環境に混ぜ込むのもどっかでやろう。

あとエクリプスプラグインはやっぱりjavaのがすごすぎるなぁ。型情報持っている=IDEが超手助けしてくれる、のはずなのにscalaときたら……とはいえ、最近Visual studio 2010 C# express 使ったんだけど、エクリプス先生に全然かなわないのね……操作を知らないだけかも知れないけど、リファクタリングのメニューの豊富さとか補完・エラー修正候補の数とか。で、Visual studio と比べるんなら、全然問題ない! って納得できなくもない。javaと比べてscalaプラグインツカエネーって言うのはかわいそうすぎるけど、やっぱりscala移行しようと思ってる人は大抵java使いのはずで、javaプラグインには勝てなくって残念。scalaのタメにIntelliJ IDEA とかちょっと敷居が高すぎるし(しかし時間があったらIntelliJいれてみる)。

続き書いた http://d.hatena.ne.jp/nazoking/20100514/1273774142