web-dev-qa-db-ja.com

列挙型のコンストラクターが静的フィールドにアクセスできないのはなぜですか?

列挙型のコンストラクターが静的フィールドと静的メソッドにアクセスできないのはなぜですか?これはクラスでは完全に有効ですが、enumでは許可されません。

私がやろうとしているのは、列挙型インスタンスを静的マップに保存することです。略語による検索を許可する次のコード例を考えてみましょう。

public enum Day {
    Sunday("Sun"), Monday("Mon"), Tuesday("Tue"), Wednesday("Wed"), Thursday("Thu"), Friday("Fri"), Saturday("Sat");

    private final String abbreviation;

    private static final Map<String, Day> ABBREV_MAP = new HashMap<String, Day>();

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
        ABBREV_MAP.put(abbreviation, this);  // Not valid
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day getByAbbreviation(String abbreviation) {
        return ABBREV_MAP.get(abbreviation);
    }
}

Enumはコンストラクターで静的参照を許可しないため、これは機能しません。ただし、クラスとして実装されている場合は検出されます:

public static final Day SUNDAY = new Day("Sunday", "Sun");
private Day(String name, String abbreviation) {
    this.name = name;
    this.abbreviation = abbreviation;
    ABBREV_MAP.put(abbreviation, this);  // Valid
}
95
Steve Kuo

静的フィールド(列挙値を表すフィールドを含む)はテキスト順で初期化され、列挙値は常に他のフィールドの前に来るため、コンストラクタは静的フィールドがすべて初期化される前に呼び出されます。クラスの例では、ABBREV_MAPが初期化される場所を示していないことに注意してください。after SUNDAYの場合、クラスが初期化されると例外が発生します。

はい、それは少し苦痛であり、おそらくより良く設計されている可能性があります。

ただし、私の経験では、すべての静的初期化子の最後にstatic {}ブロックを配置し、そこですべての値を取得するためにEnumSet.allOfを使用してすべての静的初期化を行います。

106
Jon Skeet

JLS、セクション「Enum Body Declarations」からの引用

この規則がないと、列挙型に固有の初期化循環のために、明らかに妥当なコードが実行時に失敗します。 (「自己入力」静的フィールドを持つクラスには循環が存在します。)失敗するコードの種類の例を次に示します。

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    Color() {
       colorMap.put(toString(), this);
    }
}

この列挙型の静的初期化は、NullPointerExceptionをスローします。これは、列挙定数のコンストラクターの実行時に静的変数colorMapが初期化されないためです。上記の制限により、このようなコードはコンパイルされません。

この例は、適切に機能するように簡単にリファクタリングできることに注意してください。

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    static {
        for (Color c : Color.values())
            colorMap.put(c.toString(), c);
    }
}

静的な初期化は上から下に行われるため、リファクタリングされたバージョンは明らかに正しいです。

29
Phani

多分これはあなたが望むものです

public enum Day {
    Sunday("Sun"), 
    Monday("Mon"), 
    Tuesday("Tue"), 
    Wednesday("Wed"), 
    Thursday("Thu"), 
    Friday("Fri"), 
    Saturday("Sat");

    private static final Map<String, Day> ELEMENTS;

    static {
        Map<String, Day> elements = new HashMap<String, Day>();
        for (Day value : values()) {
            elements.put(value.element(), value);
        }
        ELEMENTS = Collections.unmodifiableMap(elements);
    }

    private final String abbr;

    Day(String abbr) {
        this.abbr = abbr;
    }

    public String element() {
        return this.abbr;
    }

    public static Day elementOf(String abbr) {
        return ELEMENTS.get(abbr);
    }
}
7
user4767902

この問題は、ネストされたクラスによって解決されました。長所:短く、CPUの消費量も多いです。短所:JVMメモリ内のもう1つのクラス。

enum Day {

    private static final class Helper {
        static Map<String,Day> ABBR_TO_ENUM = new HashMap<>();
    }

    Day(String abbr) {
        this.abbr = abbr;
        Helper.ABBR_TO_ENUM.put(abbr, this);

    }

    public static Day getByAbbreviation(String abbr) {
        return Helper.ABBR_TO_ENUM.get(abbr);
    }
6
Pavel Vlasov

クラスがJVMにロードされると、静的フィールドはコードに表示される順序で初期化されます。たとえば.

public class Test4 {
        private static final Test4 test4 = new Test4();
        private static int j = 6;
        Test4() {
            System.out.println(j);
        }
        private static void test() {
        }
        public static void main(String[] args) {
            Test4.test();
        }
    }

出力は0になります。test4の初期化は静的初期化プロセスで行われ、この間jは後で表示されるため、まだ初期化されていません。ここで、jがtest4の前になるように静的イニシャライザーの順序を切り替えた場合。出力は6になりますが、Enumの場合、静的フィールドの順序を変更することはできません。列挙型の最初のものは、実際には列挙型の静的な最終インスタンスである定数でなければなりません。したがって、列挙型では、列挙型定数の前に静的フィールドが初期化されないことが常に保証されます、enumコンストラクターでそれらにアクセスしても意味がありません。

1
Hitesh