Warum kann der Konstruktor von enum nicht auf statische Felder zugreifen?

110

Warum kann der Konstruktor von enum nicht auf statische Felder und Methoden zugreifen? Dies gilt für eine Klasse, ist jedoch für eine Aufzählung nicht zulässig.

Ich versuche, meine Enum-Instanzen in einer statischen Map zu speichern. Betrachten Sie diesen Beispielcode, der die Suche nach Abkürzungen ermöglicht:

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

Dies funktioniert nicht, da enum keine statischen Referenzen in seinem Konstruktor zulässt. Es funktioniert jedoch nur, wenn es als Klasse implementiert ist:

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
}
Steve Kuo
quelle

Antworten:

113

Der Konstruktor wird aufgerufen, bevor alle statischen Felder initialisiert wurden, da die statischen Felder (einschließlich derjenigen, die die Aufzählungswerte darstellen) in Textreihenfolge initialisiert werden und die Aufzählungswerte immer vor den anderen Feldern stehen. Beachten Sie, dass Sie in Ihrem Klassenbeispiel nicht gezeigt haben, wo ABBREV_MAP initialisiert ist. Wenn es nach SONNTAG ist, erhalten Sie eine Ausnahme, wenn die Klasse initialisiert wird.

Ja, es ist ein bisschen schmerzhaft und hätte wahrscheinlich besser gestaltet werden können.

Die meiner Erfahrung nach übliche Antwort besteht jedoch darin static {}, am Ende aller statischen Initialisierer einen Block zu haben und dort die gesamte statische Initialisierung durchzuführen EnumSet.allOf , um alle Werte zu erhalten.

Jon Skeet
quelle
40
Wenn Sie eine verschachtelte Klasse hinzufügen, wird deren Statik zu einem geeigneten Zeitpunkt initialisiert.
Tom Hawtin - Tackline
Oh, schön. Daran hatte ich nicht gedacht.
Jon Skeet
3
Ein bisschen ungerade, aber wenn Sie eine statische Methode in einem Enum-Konstruktor aufrufen, die einen statischen Wert zurückgibt, wird sie gut kompiliert - aber der zurückgegebene Wert ist der Standardwert für diesen Typ (dh 0, 0.0, '\ u0000' oder null), auch wenn Sie es explizit festlegen (es sei denn, es ist als deklariert final). Ich denke, das wird schwer zu fangen sein!
Mark Rhodes
2
schnelle Ausgründungsfrage @JonSkeet: Gibt es einen Grund, den Sie EnumSet.allOfanstelle von verwenden Enum.values()? Ich frage, weil valueses eine Art Enum.class
Phantommethode ist
1
@Chirlo Es gibt eine Frage dazu . Es scheint Enum.values()schneller zu sein, wenn Sie vorhaben, sie mit einer erweiterten for-Schleife zu durchlaufen (da sie ein Array zurückgibt), aber hauptsächlich geht es um Stil und Anwendungsfall. Es ist wahrscheinlich besser zu verwenden, EnumSet.allOf()wenn Sie Code schreiben möchten, der in der Java-Dokumentation vorhanden ist, anstatt nur in den Spezifikationen, aber viele Leute scheinen Enum.values()sowieso damit vertraut zu sein.
4castle
31

Zitat aus JLS, Abschnitt "Enum Body Declarations" :

Ohne diese Regel würde anscheinend vernünftiger Code zur Laufzeit aufgrund der Initialisierungszirkularität, die Aufzählungstypen innewohnt, fehlschlagen. (Eine Zirkularität existiert in jeder Klasse mit einem "selbst typisierten" statischen Feld.) Hier ist ein Beispiel für die Art von Code, die fehlschlagen würde:

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

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

Die statische Initialisierung dieses Aufzählungstyps würde eine NullPointerException auslösen, da die statische Variable colorMap nicht initialisiert wird, wenn die Konstruktoren für die Aufzählungskonstanten ausgeführt werden. Die obige Einschränkung stellt sicher, dass dieser Code nicht kompiliert wird.

Beachten Sie, dass das Beispiel leicht überarbeitet werden kann, um ordnungsgemäß zu funktionieren:

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

Die überarbeitete Version ist eindeutig korrekt, da die statische Initialisierung von oben nach unten erfolgt.

Phani
quelle
9

Vielleicht ist es das, was du willst

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);
    }
}
user4767902
quelle
Die Verwendung Collections.unmodifiableMap()ist hier eine sehr gute Praxis. +1
4castle
Genau das, wonach ich gesucht habe. Ich sehe auch gerne Collections.unmodutableMap. Danke dir!
LethalLima
6

Das Problem wurde über eine verschachtelte Klasse gelöst. Vorteile: Es ist kürzer und auch besser durch den CPU-Verbrauch. Nachteile: eine weitere Klasse im JVM-Speicher.

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);
    }
Pavel Vlasov
quelle
1

Wenn eine Klasse in die JVM geladen wird, werden statische Felder in der Reihenfolge initialisiert, in der sie im Code angezeigt werden. Zum Beispiel

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

Die Ausgabe ist 0. Beachten Sie, dass die Initialisierung von Test4 im statischen Initialisierungsprozess stattfindet und während dieser Zeit j noch nicht initialisiert ist, wie es später erscheint. Wenn wir nun die Reihenfolge der statischen Initialisierer so ändern, dass j vor test4 steht. Die Ausgabe ist 6.Aber bei Enums können wir die Reihenfolge der statischen Felder nicht ändern. Das erste, was in enum steht, müssen die Konstanten sein, die tatsächlich statische Endinstanzen des Aufzählungstyps sind. Daher wird für Aufzählungen immer garantiert, dass statische Felder nicht vor Aufzählungskonstanten initialisiert werden. Da wir statischen Feldern keine sinnvollen Werte für die Verwendung im Aufzählungskonstruktor geben können Es wäre sinnlos, im Enum-Konstruktor auf sie zuzugreifen.

Hitesh
quelle