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


110

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

私がやろうとしていることは、列挙型インスタンスを静的マップに格納することです。略語による検索を可能にする次のコード例を検討してください。

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
}

回答:


113

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

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

ただし、私の経験ではstatic {}、すべての静的イニシャライザの最後にブロックを置き、そこですべての静的初期化を実行EnumSet.allOf して、すべての値を取得するというのが通常の答えです。


40
ネストされたクラスを追加すると、その静的クラスは適切なときに初期化されます。
トムホーティン-タックライン

ああ、いいですね。私はそのことを考えていませんでした。
Jon Skeet、

3
少し奇妙なものですが、静的な値を返す列挙型コンストラクタで静的メソッドを呼び出すと、コンパイルは正常に行われますが、返される値はその型のデフォルト値になります(つまり、0、0.0、 '\ u0000'またはnull)、明示的に設定した場合でも(として宣言されている場合を除くfinal)。捕まえるのは難しいと思います!
マークロードス

2
簡単なスピンオフの質問@JonSkeet:EnumSet.allOf代わりに使用する理由はありますEnum.values()か?ので、私は尋ねるvalues種類ファントム法のである(ソースを見ることができないEnum.class)、その作成したとき、私は知らない
Chirlo

1
@Chirlo それについて質問があります。Enum.values()拡張されたforループを使用してそれらを反復することを計画している場合は(配列を返すため)より高速であるように見えますが、ほとんどはスタイルとユースケースについてです。EnumSet.allOf()仕様だけでなく、Javaのドキュメントに存在するコードを記述したい場合に使用する方がおそらく良いでしょうが、多くの人はEnum.values()とにかく慣れているようです。
4castle

31

JLS、セクション「列挙体宣言」からの引用:

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

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);
    }
}

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


9

多分これはあなたが欲しいものです

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);
    }
}

ここでの使用Collections.unmodifiableMap()は非常に良い習慣です。+1
キャッスル2016年

まさに私が探していたもの。Collections.unmodifiableMapを見るのも好きです。ありがとうございました!
LethalLima

6

ネストされたクラスによって解決された問題。長所: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);
    }

1

クラスが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型の静的な最終インスタンスである定数である必要があります。したがって、enumの場合は、静的フィールドがenum定数の前に初期化されないことが常に保証されます。 、列挙型コンストラクタでそれらにアクセスしても意味がありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.