it-swarm.com.de

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

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

Ich versuche, meine Enum-Instanzen in einer statischen Map zu speichern. Betrachten Sie diesen Beispielcode, der eine Suche nach Abkürzung 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
}
95
Steve Kuo

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 wird - wenn es sich um nach SONNTAG handelt, erhalten Sie eine Ausnahme, wenn die Klasse initialisiert wird.

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

Nach meiner Erfahrung lautet die übliche Antwort jedoch, einen static {} - Block am Ende aller statischen Initialisierer zu haben und dort die gesamte statische Initialisierung mit EnumSet.allOf Durchzuführen, um alle Werte abzurufen.

106
Jon Skeet

Zitat aus JLS, Abschnitt "Enum Body Declarations" :

Ohne diese Regel würde anscheinend vernünftiger Code zur Laufzeit aufgrund der Initialisierungszirkularität von Aufzählungstypen fehlschlagen. (Eine Zirkularität existiert in jeder Klasse mit einem "selbsttypisierten" 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 ein solcher 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.

29
Phani

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

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

Wenn eine Klasse in die JVM geladen wird, werden statische Felder in der Reihenfolge initialisiert, in der sie im Code angezeigt werden. Für z.B.

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 Test4-Initialisierung im statischen Initialisierungsprozess stattfindet und während dieser Zeit j noch nicht initialisiert ist, wie es später erscheint. Nun, wenn wir die Reihenfolge der statischen Initialisierer so ändern, dass j vor test4 kommt. Die Ausgabe wird 6.Aber im Falle von Enums können wir die Reihenfolge der statischen Felder nicht ändern. Das erste, was in enum enthalten sein muss, sind die Konstanten, die tatsächlich statische Endinstanzen des Typs enum sind. Für enums ist daher immer gewährleistet, dass statische Felder nicht vor enum-Konstanten initialisiert werden Es wäre sinnlos, im Enum-Konstruktor darauf zuzugreifen.

1
Hitesh