JavaのEnumにおける初期化の順番

  • このエントリーをはてなブックマークに追加

この前ハマったので、備忘録メモ。
http://stackoverflow.com/questions/6435267/java-enum-static-final-instance-variables

EnumをEnum名(.name())とは別の特定のキーから逆引きできるコードを書きたくて、以下のように書いてみました。

import java.util.HashMap;
import java.util.Map;

public enum MyEnum {
ATTR1("aliasA", "aliasB"),
ATTR2("aliasC")
;
private static final Map<String, MyEnum> lookupMap = new HashMap<>();
private MyEnum(String... args) {
for (String s: args) {
lookupMap.put(s, this); // コンストラクタでstatic変数を参照
}
}
public static MyEnum lookup(String s) {
return lookupMap.get(s);
}
}

すると、こんなエラーが出ました。

Cannot refer to the static enum field MyEnum.lookupMap within an initializer

コンストラクタ内でstatic変数を参照しているのがNGのようです。
通常のクラスの場合、staticイニシャライザが最初に実行されるのですが、
Enumの場合は、staticイニシャライザよりもinstanceのコンストラクタの方が先に実行されるようです。

というわけで、こう書きます。

import java.util.HashMap;
import java.util.Map;

public enum MyEnum {
ATTR1("aliasA", "aliasB"),
ATTR2("aliasC")
;
private static final Map<String, MyEnum> lookupMap = new HashMap<>();
private final String[] aliases;
static {
for (MyEnum e : values()) { // 全てのEnum要素を列挙して
for (String alias: e.aliases) { // 各Enumのインスタンス変数を参照し
lookupMap.put(alias, e); // Lookupテーブルに格納する
}
}
}
private MyEnum(String... args) {
this.aliases = args; // インスタンス変数に待避
}
public static MyEnum lookup(String s) {
return lookupMap.get(s);
}
}

もしくは、InnerClassでHolderクラスを作ります。こっちの方がスマートですね。
この場合、Holder#static{}が一番最初に呼び出されるので、MyEnumコンストラクタから直接参照できるわけです。

import java.util.HashMap;
import java.util.Map;

public enum MyEnum {
ATTR1("aliasA", "aliasB"),
ATTR2("aliasC")
;
private static final class Holder {
private static final Map<String, MyEnum> lookupMap = new HashMap<>();
}
private MyEnum(String... args) {
for (String alias : args) {
Holder.lookupMap.put(alias, this);
}
}
public static MyEnum lookup(String s) {
return lookupMap.get(s);
}
}

初期化順序確認コード

Enumと普通のクラスの初期化順を確認するコードです。

public class InitOrderTest {
public static enum MyEnum {
A, B, C;
static {
System.out.println("enum-static-init");
}
private MyEnum() {
System.out.println("enum-constructor");
}
{
System.out.println("enum-init");
}
}
public static class MyClass {
static {
System.out.println("class-static-init");
}
MyClass() {
System.out.println("class-constructor");
}
{
System.out.println("class-init");
}
}
public static void main(String[] args) {
System.out.println("main");
MyEnum.values();
new MyClass();
}
}

classの場合は、static -> {} -> ()という順番に対して、Enumの場合は、{} -> () -> staticという順番になっています。

main
enum-init
enum-constructor
enum-init
enum-constructor
enum-init
enum-constructor
enum-static-init
class-static-init
class-init
class-constructor