6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ScalaAdvent Calendar 2015

Day 9

object がどうクラスファイルにコンパイルされるか

Posted at

埋まってなかったので八日目を埋めます。今日は九日です。時空の歪みです。
表題通り。昔色々調べてた時にやったはずなんですが、だいぶ忘れちゃってるので調べなおした。調べなおしたら昔とちょっと変わっててびっくりです。

実は scala の repl には ":javap" というコマンドがあって、repl の中から色々と調べられるんですが、Constant pool とかその他諸々出てきて長くなるし、普通に javap を利用します。慣れてるし。

とりあえず適当なコードを準備する。

object.scala
class Class {
  object InnerObject {
    def succ(n: Int) = n + 1
  }
}
object OuterObject {
  def succ(n: Int) = n + 1
}

コンパイル。

C:\Users\m\src\scala>scalac -version
Scala compiler version 2.11.7 -- Copyright 2002-2013, LAMP/EPFL

C:\Users\m\src\scala>scalac object.scala

とりあえず OuterObject から。

C:\Users\m\src\scala>javap -c OuterObject
Compiled from "object.scala"
public final class OuterObject extends java.lang.Object{
public static int succ(int);
Code:
0: getstatic #16; //Field OuterObject$.MODULE$:LOuterObject$;
3: iload_0
4: invokevirtual #18; //Method OuterObject$.succ:(I)I
7: ireturn

}

OuterObject::succ メソッドは OuterObject$::succ メソッドに委譲されてますね。イメージとしてはこんな感じ。

public static int succ(int n) {
  return OuterObject$.MODULE$.succ(n);
}

OutetObject$ を見てみましょう。

C:\Users\m\src\scala>javap -c OuterObject\$
Compiled from "object.scala"
public final class OuterObject\$ extends java.lang.Object{
public static final OuterObject\$ MODULE\$;

public static {};
  Code:
   0:   new     #2; //class OuterObject\$
   3:   invokespecial   #12; //Method "<init>":()V
   6:   return

public int succ(int);
  Code:
   0:   iload_1
   1:   iconst_1
   2:   iadd
   3:   ireturn

}

こっちが実装になります。Static Initializer で初期化されてますね。

次に class 内に object が定義されている場合。こちらはトップレベルで定義されている場合と違い、クラス毎にオブジェクトを持つことになるので色々変わってきます。

C:\Users\m\src\scala>javap -c Class
Compiled from "object.scala"
public class Class extends java.lang.Object{
public Class\$InnerObject\$ InnerObject();
  Code:
   0:   aload_0
   1:   getfield        #14; //Field InnerObject\$module:LClass\$InnerObject\$;
   4:   ifnonnull       14
   7:   aload_0
   8:   invokespecial   #33; //Method InnerObject\$lzycompute:()LClass\$InnerObjec
t$;
   11:  goto    18
   14:  aload_0
   15:  getfield        #14; //Field InnerObject\$module:LClass\$InnerObject\$;
   18:  areturn

public Class();
  Code:
   0:   aload_0
   1:   invokespecial   #36; //Method java/lang/Object."<init>":()V
   4:   return

}

なんか InnerObject を一度だけ初期化しようとしているような感じになってますが、javap 君はちょっと変なシンボルがくるとすぐに根をあげるので lzycompute の定義が見当たりませんね、困りました。他のツールで逆コンパイルなりしてもいいんですが、どのツールもなかなかどうして完璧には仕事をしてくれない。仕方ないんですが。別にバイトコードまでみたいわけじゃない、雰囲気が分かればいいということであれば scalac -Xshow:jvm とかするといいです。

C:\Users\m\src\scala>scalac -Xprint:jvm object.scala
[[syntax trees at end of                       jvm]] // object.scala
package <empty> {
  class Class extends Object {
    private def InnerObject\$lzycompute(): Class\$InnerObject.type = {
      {
        Class.this.synchronized({
          if (Class.this.InnerObject\$module.eq(null))
            {
              Class.this.InnerObject\$module = new Class\$InnerObject.type(Class.this);
              ()
            };
          scala.runtime.BoxedUnit.UNIT
        });
        null
      };
      Class.this.InnerObject$module
    };
    @volatile <synthetic> private[this] var InnerObject$module: Class$InnerObject.type = _;
    <stable> def InnerObject(): Class$InnerObject.type = if (Class.this.InnerObject$module.eq(null))
      Class.this.InnerObject$lzycompute()
    else
      Class.this.InnerObject$module;
    def <init>(): Class = {
      Class.super.<init>();
      ()
    }
  };
  object OuterObject extends Object {
    def succ(n: Int): Int = n.+(1);
    def <init>(): OuterObject.type = {
      OuterObject.super.<init>();
      ()
    }
  };
  object Class$InnerObject extends Object {
    def succ(n: Int): Int = n.+(1);
    def <init>($outer: Class): Class$InnerObject.type = {
      Class$InnerObject.super.<init>();
      ()
    }
  }
}

なんかそれっぽいものが出てきました。やったぜ。なんとなく読めると思います。

後一応 Class#InnerObject も。

C:\Users\m\src\scala>javap -c Class$InnerObject$
Compiled from "object.scala"
public class Class$InnerObject$ extends java.lang.Object{
public int succ(int);
  Code:
   0:   iload_1
   1:   iconst_1
   2:   iadd
   3:   ireturn

public Class$InnerObject$(Class);
  Code:
   0:   aload_0
   1:   invokespecial   #16; //Method java/lang/Object."<init>":()V
   4:   return

}

まあ特にいうこともなく。

さて InnerObject のようにクラス内で定義された object は、普通にインスタンスにアクセッサーがあるので、それを呼べば java からでも触れることができます。
ところがトップレベルで定義された OuterObject の場合(同名のクラス定義を伴うコンパニオンオブジェクトなども同様)、どうすればいいか。昔は OuterObject$.MODULE$ とかにアクセスするしかなかったんですね。酷い話だ。今は OuterObject クラスが生成されるようになったので、素直にこのクラスの static メソッドを呼び出すことができます。コンパニオンオブジェクトの場合も、きちんと OuterObject$ に委譲するメソッドが生成されます。

昔の事情については Scala School などを参照してください。

一年以上 Scala なんて触ってなかったので普通に常識だったらすいません。おしまい。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?