2
0

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 1 year has passed since last update.

Kotlin の enum class のインスタンス生成は singleton か

Last updated at Posted at 2022-06-13

Kotlin の enum class のインスタンスの生成は,呼び出されるたびに行われるのでしょうか,それとも一度生成されて static なかたちで保持されるのでしょうか.

ドキュメントに載っているかなと思ったのですが特に言及されていないように見えたので bytecode を見てみようと思いました.

用意したコード

次のようなコードを用意しました.

enum class MyEnum(val prop: String) {
    Enum1(prop = makeProp("Enum1")),
    Enum2(prop = makeProp("Enum2"))
}

class MyClass(val prop: String) {
    companion object {
        val MyClass1 = MyClass(makeProp("MyClass1"))
    }
}

fun makeProp(name: String) : String {
    println(">>> makeProp: $name...")
    return "Prop of $name"
}

fun fEnumClass() {
    println(">> 1: fEnumClass")
    val e = MyEnum.Enum1
    println(">> prop = ${e.prop}")
}

fun fNewClass() {
    println(">> 2: fNewClass")
    val c = MyClass(makeProp("MyClass"))
    println(">> prop = ${c.prop}")
}

fun fStaticClass() {
    println(">> 3: fStaticClass")
    val c = MyClass.MyClass1
    println(">> prop = ${c.prop}")
}

fun main() {
    println("> begin")
    fEnumClass()
    fEnumClass()
    fNewClass()
    fNewClass()
    fStaticClass()
    fStaticClass()
    println("> end")
}
  1. fEnumClassenum class を参照して使うケース,
  2. fStaticClasscompanion objectstatic にインスタンスを持つケースです.
  3. fNewClass は関数実行ごとに毎回 new するケースです.

それぞれ 2 回ずつ呼び出すことで、生成処理の makeProp 内の println の出力が
1 度しか呼び出されないか、それとも毎回呼び出されるか確認してみます。

実行結果

このコードの実行結果は以下のようになります:

> begin
>> 1: fEnumClass
>>> makeProp: Enum1...
>>> makeProp: Enum2...
>> prop = Prop of Enum1
>> 1: fEnumClass
>> prop = Prop of Enum1
>> 2: fNewClass
>>> makeProp: MyClass1...
>>> makeProp: MyClass...
>> prop = Prop of MyClass
>> 2: fNewClass
>>> makeProp: MyClass...
>> prop = Prop of MyClass
>> 3: fStaticClass
>> prop = Prop of MyClass1
>> 3: fStaticClass
>> prop = Prop of MyClass1
> end

こうして見ると,ログの流れから以下の結果がわかります:

  • enum class:
    • 初期化は最初に参照された 1 度だけ行われている
    • また 参照されていない Enum2 の初期化も行われている
  • new:
    • 初期化は関数での実行ごとに毎回行われている
  • companion object:
    • 初期化は class が参照されたタイミングで 1 度だけ初期化されている

ですから、enum classcompanion object で初期化されるように singleton であると予想できます。

enum class

enum class の出力する初期化処理の bytecode も見てみます:

public final enum MyEnum extends java/lang/Enum {
  // (略)
  // access flags 0x8
  static <clinit>()V
   // (略)
   L1
    LINENUMBER 3 L1
    LDC "Enum2"
    INVOKESTATIC MainKt.makeProp (Ljava/lang/String;)Ljava/lang/String;
    INVOKESPECIAL MyEnum.<init> (Ljava/lang/String;ILjava/lang/String;)V
    DUP
    PUTSTATIC MyEnum.Enum2 : LMyEnum;
    AASTORE
    PUTSTATIC MyEnum.$VALUES : [LMyEnum;
    RETURN
    MAXSTACK = 9
    MAXLOCALS = 0
  // (略)
}

L1 あたりの Enum2 を生成する部分に注目しています。PUTSTATIC が呼び出されています。

  // access flags 0x19
  public final static fEnumClass()V
   // 略
   L4
    LINENUMBER 19 L4
    GETSTATIC MyEnum.Enum1 : LMyEnum;
    ASTORE 0

また fEnumClass() 内で MyEnum.Enum1 を参照している部分はこのように GETSTATIC で参照されます。

companion object

companion object の初期化の出力する bytecode を見てみます:

public final class MyClass {
  // (略)
  // access flags 0x8
  static <clinit>()V
  // (略)
   L0
    LINENUMBER 8 L0
    NEW MyClass
    DUP
    LDC "MyClass1"
    INVOKESTATIC MainKt.makeProp (Ljava/lang/String;)Ljava/lang/String;
    INVOKESPECIAL MyClass.<init> (Ljava/lang/String;)V
    PUTSTATIC MyClass.MyClass1 : LMyClass;
    RETURN
    MAXSTACK = 3
    MAXLOCALS = 0
  // (略)
}

L0MyClass1 を生成する部分に似通ったものを感じます。

  // access flags 0x1019
  public final static synthetic access$getMyClass1$cp()LMyClass;
   L0
    LINENUMBER 6 L0
    GETSTATIC MyClass.MyClass1 : LMyClass;
    ARETURN
  // (略)

また fStaticClass() から呼び出される MyClass.MyClass1 を取得する部分も GETSTATIC が使われています。

通常の new

関数内で new する場合の初期化処理の bytecode を見てみます:

public final class MyClass {

  // access flags 0x1
  public <init>(Ljava/lang/String;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "prop"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 6 L1
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ALOAD 0
    ALOAD 1
    PUTFIELD MyClass.prop : Ljava/lang/String;
    RETURN
   L2
    LOCALVARIABLE this LMyClass; L0 L2 0
    LOCALVARIABLE prop Ljava/lang/String; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

fNewClass の bytecode もみてみます:

  // access flags 0x19
  public final static fNewClass()V
   L0
    LINENUMBER 24 L0
    LDC ">> 2: fNewClass"
    ASTORE 0
   // (略)
   L3
   L4
    LINENUMBER 25 L4
    NEW MyClass
    DUP
    LDC "MyClass"
    INVOKESTATIC MainKt.makeProp (Ljava/lang/String;)Ljava/lang/String;
    INVOKESPECIAL MyClass.<init> (Ljava/lang/String;)V
    ASTORE 0
   L5

かなり異なったインストラクションであるように見えます。

全体感

こうして見ると,enum classcompanion object で保持する場合に出力される bytecode は同じ流れに見え,意味合い的にも <clinit> で初期化して PUTSTATIC あたりで static に保存するという部分が同じに思えます (bytecode の知識は全然ないので間違ってたら教えてください!).

なので,enum class では参照されたときにすべてのメンバーが初期化されて Singleton にインスタンスが保持されるということがわかりました。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?