読者です 読者をやめる 読者になる 読者になる

Scalaのtraitはどのようなclassファイルに変換されるか

おもしろい順に記事を投稿していたら、とっても古くなってしまいました。(´・_・`) しかし、内容自体は古くなってはいないと思うのでそのまま投稿します。(ง ˘ω˘ )ว

Scala2.12とかJava8とかデフォルト実装付きinterfaceとかは知りません(・ ω ・)

traitの実装

Scalaが実際にはどのようにtraitを実現しているのかを調べるために、

object A {
  trait X {
    def xx() = "xx"
  }
 
  def main(args: Array[String]) {
    val x = new X {}
  }
}

がどうなっているかを調べました。

生成されるファイル

> ls target/scala-2.11/classes/

をしてみると

  • A.class
  • A$.class
  • A$X.class
  • A$X$class.class
  • A$$anon$1.class

が生成されていることが分かりました。

具体的な内容

A.classA$.classobject Aの実装を担当しています。(mainメソッドとかもここに)

次に、A$X

javap -p A$X

で調べると

public interface A$X {
  public abstract java.lang.String xx();
}

となっていました。

同様に、A$X$class

public abstract class A$X$class {
  public static java.lang.String xx(A$X);
    Code:
       0: ldc           #8                  // String xx
       2: areturn
 
  public static void $init$(A$X);
    Code:
       0: return
}

となっていてtrait Xコンパイル時にinterfaceとabstract classに分離するらしいということが分かりました。(最適化とかいろいろあると思うのでいつもこうとは限らないかもしれません・・。)

そしてnew X {}を担当するA$$anon$1

public final class A$$anon$1 implements A$X {
  public java.lang.String xx();
    Code:
       0: aload_0
       1: invokestatic  #19                 // Method A$X$class.xx:(LA$X;)Ljava/lang/String;
       4: areturn
 
  public A$$anon$1();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokestatic  #29                 // Method A$X$class.$init$:(LA$X;)V
       8: return
}

となっていました。これをよく見てみると、

1.tarit XのinterfaceであるA$Xをimplementsする 2.A$Xインターフェースで必要になっているxxを自分で実装する(A$X$classをoverrideするのではなく) 3.A$X$classxxメソッドがstaticになっているのでinvokestaticで呼び出す

のような順番で呼ばれていることが分かります。

staticだと困るんじゃないかと思いましたが、 A$X$classxxメソッドがいつのまにか public static java.lang.String xx(A$X)となっていて引数にA$Xを受け取るようになっています。

実験ベースなので普遍性があるのかやや不明ですが、とりあえず今回の例ではこのようになることが分かりました。