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

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

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


## 用意したコード

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

```kotlin
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. `fEnumClass` は `enum class` を参照して使うケース，
2. `fStaticClass` は `companion object` で `static` にインスタンスを持つケースです．
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 class` は `companion 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`](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.putstatic) が呼び出されています。

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

また `fEnumClass()` 内で `MyEnum.Enum1` を参照している部分はこのように [`GETSTATIC`](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.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
  // (略)
}
```

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

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

また `fStaticClass()` から呼び出される `MyClass.MyClass1` を取得する部分も [`GETSTATIC`](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.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 class` と `companion object` で保持する場合に出力される bytecode は同じ流れに見え，意味合い的にも `<clinit>` で初期化して [`PUTSTATIC`](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.putstatic) あたりで static に保存するという部分が同じに思えます (bytecode の知識は全然ないので間違ってたら教えてください！)．

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