以前、こんなエントリを書きました。
Scalaから、Javaのインターフェースに定義されたeq(Object)メソッドを呼び出せない?
http://d.hatena.ne.jp/Kazuhira/20131215/1387109792
Scalaでは、AnyRefに
final def eq(that: AnyRef): Boolean
というメソッドが定義されているのですが、Javaでインターフェースに同名のメソッドがあった場合はうまく呼び出せないという話でした。
Stack Overflowでも。
How to call T eq(Object) method of Java interface from Scala?
http://stackoverflow.com/questions/7263861/how-to-call-t-eqobject-method-of-java-interface-from-scala
なお、インターフェースを実装したクラスにダウンキャストしてあげると、ちゃんと呼び出すことができます。つまり、インターフェースの型で扱ったままだとダメなのです。
個人的には、Javaのクエリを組み立てるDSLでこれにぶつかり、代替のメソッド(意味違うけど)に逃げてることが多いです…。
で、実際解決するとしたらどうしようかなぁ〜ということで、大して考えてませんが、対応策を。
やっぱり、Implicit Class+Value Class?
対象とするJavaのインターフェースとしては、前回同様こんなのを考えます。
src/main/java/HasEqInterface.java
public interface HasEqInterface { String eq(Object target); }
引数の型がObject、戻り値の型がStringのeqメソッドです。
その実装クラス。
src/main/java/HasEqImpl.java
public class HasEqImpl implements HasEqInterface { private String value; public HasEqImpl(String value) { this.value = value; } @Override public String eq(Object target) { return Boolean.valueOf(value.equals(target.toString())) .toString(); } }
で、これに対してこんなScalaコードを書くと
src/test/scala/AvoidHasEqSpec.scala
import org.scalatest.FunSpec import org.scalatest.Matchers._ class AvoidHasEqSpec extends FunSpec { describe("avoid has eq") { it("test") { val hasEq: HasEqInterface = new HasEqImpl("Hello World") val result: String = hasEq.eq("Hello World") result should be ("true") } } }
まあ、コンパイルエラーです。
> test [info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/test-classes... [error] /xxxxx/src/test/scala/AvoidHasEqSpec.scala:10: type mismatch; [error] found : Boolean [error] required: String [error] val result: String = hasEq.eq("Hello World") [error] ^ [error] one error found [error] (test:compile) Compilation failed [error] Total time: 1 s, completed 2014/03/01 16:49:23
ちなみに、こんな形で推論させると
val result = hasEq.eq("Hello World")
警告されます。
[info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/test-classes... [warn] /xxxxx/src/test/scala/AvoidHasEqSpec.scala:10: HasEqInterface and String are unrelated: they will most likely never compare equal [warn] val result = hasEq.eq("Hello World") [warn] ^
ムリに動かしても、Booleanが戻ってくることになりますが。
で、このままだとどうやってもHasEqInterface#eqメソッドを呼べないので(ここでは実体の型にダウンキャストすることは、考えないことにします)、Javaでヘルパー的なものを作ります。
src/main/java/HasEqHelper.java
public class HasEqHelper { public static String eqDelegate(HasEqInterface hasEq, Object target) { return hasEq.eq(target); } }
この後で作るScalaのブリッジコードから呼べるように、「eqDelegate」という名前に。
これに対して、ScalaでImplicit ClassとValue Classで変換を被せます。
src/main/scala/HasEqHelperScala.scala
object HasEqHelperScala { implicit class HasEqWrapper(val underlying: HasEqInterface) extends AnyVal { def equal(target: AnyRef): String = HasEqHelper.eqDelegate(underlying, target) } }
eqメソッドだと結局ダメなので、ここでは「equal」と別名にすることにしました。
src/test/scala/AvoidHasEqSpec.scala
import org.scalatest.FunSpec import org.scalatest.Matchers._ import HasEqHelperScala._ class AvoidHasEqSpec extends FunSpec { describe("avoid has eq") { it("test") { val hasEq: HasEqInterface = new HasEqImpl("Hello World") //val result: String = hasEq.eq("Hello World") val result: String = hasEq.equal("Hello World") result should be ("true") } } }
これで、動かすことができます。
> test [info] Compiling 1 Java source to /xxxxx/target/scala-2.10/classes... [info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/test-classes... [info] AvoidHasEqSpec: [info] avoid has eq [info] - test [info] Run completed in 348 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [success] Total time: 3 s, completed 2014/03/01 17:03:03
まあ、実際困ったらこんな逃げ方をするんですかねぇ…。
一応確認として、sbtでの設定時に
scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-Xprint:jvm")
として途中のコードを確認。
うーん。
<synthetic> object HasEqWrapper extends Object { final def equal$extension($this: HasEqInterface, target: Object): String = HasEqHelper.eqDelegate($this, target); final <synthetic> def hashCode$extension($this: HasEqInterface): Int = $this.hashCode(); final <synthetic> def equals$extension($this: HasEqInterface, x$1: Object): Boolean = { case <synthetic> val x1: Object = x$1; case5(){ if (x1.$isInstanceOf[HasEqWrapper]()) matchEnd4(true) else case6() };
あとは、呼んでるところ。
val result: String = HasEqWrapper.this.equal$extension(HasEqHelperScala.HasEqWrapper(hasEq), "Hello World");
新しいインスタンスは作ってないから、大丈夫そうですね。
Javaでコードを書かずに回避はできない気がしますが、いざとなったらこんな感じで対処でしょう。