Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

コンパニオンオブジェクトで定義されたメソッドは static メソッドではない?

ただ単に「そうなんだあ」と思った話です。Scala のコンパニオンオブジェクト定義されたメソッドは、Java 上で static メソッドとして定義されるわけではない、という話です。static のようなものだよ、と説明を受けるのですが、その内実はじつは、ただのシングルトンです。

今、ScalaJava で抽象構文木の実装をしていて、よくある PrimaryExpr (呼び方・名付け方は諸説あります) を実装しています。元のクラスはこんな感じ。

  object PrimaryExpr {

    def newInstance(c: List[ASTree]): PrimaryExpr = new PrimaryExpr(c.asJava)

    def create(_c: java.util.List[ASTree]): ASTree = {
      if (_c.size == 1) {
        _c.get(0)
      } else {
        new PrimaryExpr(_c)
      }
    }
  }

  case class PrimaryExpr(_c: java.util.List[ASTree]) extends ASTList(_c) {}

書いてませんが、sealed class で ASTList というクラスも保持しています。それで、Java のパーサーコンビネータ側で PrimaryExpr$#create を呼ぼうと下記リフレクションを行いました。(factoryName には、"create" が入っています。)

final Method m = clazz.getMethod(factoryName,
                            new Class<?>[]{argType});
        return new Factory() {
            protected ASTree make0(Object arg) throws Exception {
                return (ASTree) m.invoke(null, arg);
            }
};

上述のように m.invoke(null, arg) をすると、そのクラス内の同名の static メソッドを呼び出しできるはずなのですが、なぜかうまくいきません。Scala のコンパニオンオブジェクトは Java の static なものを表現するものだと思っていたので、当然これでうまくいくはず…と思っていたのですが、なぜかうまくいきません。

こういう JavaScala の連携時は私の知らない様々な不明点がたくさんあるので、とりあえずデコンパイルしてみましょう。デコンパイル結果は下記です。

public class ASTList$PrimaryExpr$
  implements Serializable
{
  public static  MODULE$;
  
  private Object readResolve()
  {
    return MODULE$;
  }
  
  public ASTList.PrimaryExpr newInstance(scala.collection.immutable.List<ASTree> c)
  {
    return new ASTList.PrimaryExpr((java.util.List)JavaConverters..MODULE$.seqAsJavaListConverter(c).asJava());
  }
  
  public ASTree create(java.util.List<ASTree> _c)
  {
    return _c.size() == 1 ? (ASTree)_c.get(0) : new ASTList.PrimaryExpr(_c);
  }
  
  public ASTList.PrimaryExpr apply(java.util.List<ASTree> _c)
  {
    return new ASTList.PrimaryExpr(_c);
  }
  
  public Option<java.util.List<ASTree>> unapply(ASTList.PrimaryExpr x$0)
  {
    return x$0 == null ? None..MODULE$ : new Some(x$0._c());
  }
  
  public ASTList$PrimaryExpr$()
  {
    MODULE$ = this;
  }
  
  static
  {
    new ();
  }
}

なんと、どうやら create メソッドは static ではないんですね。コンパニオンオブジェクトは全体的に static として処理されるのかと思っていたのでちょっと違って驚きでした。

このサイト (Scalaオブジェクトメモ(Hishidama's Scala object Memo)) によると、

Scalaのオブジェクトのメンバーは、Javaのstaticメンバーの様に扱えるように見える。[2011-01-18]
つまり「newでインスタンスを生成してそのインスタンスのメソッドを呼ぶ」のではなく、「クラス名(オブジェクト)を指定して、そのメソッドを呼ぶ」ようなコーディングになる。

しかしシングルトンオブジェクトはあくまで(唯一の)オブジェクトなのであって、そのメンバー(フィールド・メソッド)は静的なメンバーではない。

(...)

つまりobjectはそのスコープ内で唯一のインスタンスなだけで、アプリケーション(VM)全体で常に“シングルトン(唯一)”であるわけではない。

とのことです。なるほど、では Java 側で static 呼び出しを期待してメソッド呼び出しをしている箇所を全面的に修正する必要がありそうですね。とても勉強になりました。