CLOVER🍀

That was when it all began.

Scalaから、Javaのインターフェースに定義されたeq(Object)メソッドを呼び出せない?

前回書いた、InfinispanのQuery DSLを使った時に、これにかなりハマりました。

Scalaには、AnyRefクラスにeqというメソッドが定義してあります。

  final def eq(that: AnyRef): Boolean

Scalaでクラスを書く場合は、わざわざこのメソッドと衝突するようなものを書こうと思わない(finalだし…)でしょうが、Java側で、しかもインターフェースで定義してあった場合にはちょっと苦労します。

簡単な例を出しましょう。

たとえば、こういうインターフェースを定義します。
src/main/java/HasEqInterface.java

public interface HasEqInterface {
    String eq(Object target);
}

引数がObjectの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から呼んでみます。

まずはインターフェースの型で扱うパターン。

    val interfaceVal: HasEqInterface = new HasEqImpl("interfaceVal")

eqメソッドを呼び出してみましょう。

    val result: String = interfaceVal.eq("interfaceVal")

コンパイルしてみると、

type mismatch;
[error]  found   : Boolean
[error]  required: String
[error]       val result: String = interfaceVal.eq("interfaceVal")
[error]                                           ^
[error] one error found
[error] (compile:compile) Compilation failed

コンパイルエラーになります…。どう見ても、AnyRef#eqを参照しています。

ところがですね、ここでインターフェースの型ではなく、クラスに落とすと

    val result: String = interfaceVal.asInstanceOf[HasEqImpl].eq("interfaceVal")

コンパイル可能になります。

> compile
[info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/classes...
[success] Total time: 1 s, completed 2013/12/15 21:01:13

この理屈なので、最初からクラスの型で扱っていればコンパイル可能です。

    val classVal: HasEqImpl = new HasEqImpl("classVal")

    val result: String = classVal.eq("classVal")

同じようにeqで衝突する例は、ScalaからS2JDBCや

Scala から S2JDBC を利用するときに気をつけること
http://d.hatena.ne.jp/NetPenguin/20100406

Mockitを使う場合があるようです。

SpecsでMockitoのAnswerとArgumentCaptorを使う
http://d.hatena.ne.jp/takezoe/20110614

Using Mockito in a Scala unit test
http://lizdouglass.wordpress.com/2010/12/15/using-mockito-in-a-scala-unit-test/

で、今回はもうちょっとハマる例でしたが、最終的に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のインターフェースで定義されたeq(Object)メソッドは、Scalaから呼び出す時は実装したクラスにキャストしたり、実装したクラスの型で扱いましょう
ということですね。

自分がハマったケースは、InfinispanのQuery DSLにeq(Object)なメソッドがあって、しかもインターフェース定義、とどめに実装クラスがパッケージプライベートのため見えなくて、完全に詰みました…。

このインターフェースで定義されたeq(Object)メソッド

FilterConditionEndContext
https://github.com/infinispan/infinispan/blob/master/query-dsl/src/main/java/org/infinispan/query/dsl/FilterConditionEndContext.java

を実装した、このクラスが見えないからです…。

AttributeCondition
https://github.com/infinispan/infinispan/blob/master/query-dsl/src/main/java/org/infinispan/query/dsl/impl/AttributeCondition.java

というわけで、先のInfinispan Query DSLの使用時にはeqメソッドを使うことは諦めましたが(同じパッケージにすれば、もちろん使えましたが…)、投げるQueryの内容が他のメソッドでも代用できることがわかったので、それで逃げました。

ORMとかでDSLを組んでいるものを、Scalaから使う時は注意かもしれません…。