Perché il costruttore di enum non può accedere ai campi statici?


110

Perché il costruttore di enum non può accedere a campi e metodi statici? Questo è perfettamente valido con una classe, ma non è consentito con un'enumerazione.

Quello che sto cercando di fare è memorizzare le mie istanze di enum in una mappa statica. Considera questo codice di esempio che consente la ricerca per abbreviazione:

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

Questo non funzionerà poiché enum non consente riferimenti statici nel suo costruttore. Tuttavia funziona solo se implementato come classe:

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
}

Risposte:


113

Il costruttore viene chiamato prima che i campi statici siano stati tutti inizializzati, perché i campi statici (inclusi quelli che rappresentano i valori enum) vengono inizializzati in ordine testuale, ei valori enum vengono sempre prima degli altri campi. Nota che nel tuo esempio di classe non hai mostrato dove ABBREV_MAP è inizializzato - se è dopo SUNDAY, otterrai un'eccezione quando la classe viene inizializzata.

Sì, è un po 'fastidioso e probabilmente avrebbe potuto essere progettato meglio.

Tuttavia, la solita risposta nella mia esperienza è di avere un static {}blocco alla fine di tutti gli inizializzatori statici e fare tutte le inizializzazioni statiche lì, usando EnumSet.allOf per ottenere tutti i valori.


40
Se aggiungi una classe annidata, le sue statistiche verranno inizializzate al momento opportuno.
Tom Hawtin - tackline

Ooh, carino. Non ci avevo pensato.
Jon Skeet

3
Un po 'strano ma se chiami un metodo statico in un costruttore di enumerazione che restituisce un valore statico, verrà compilato correttamente, ma il valore restituito sarà quello predefinito per quel tipo (cioè 0, 0.0,' \ u0000 'o null), anche se lo imposti esplicitamente (a meno che non sia dichiarato come final). Immagino che sarà difficile da catturare!
Mark Rhodes

2
domanda rapida di spin-off @ JonSkeet: Qualche motivo che usi al EnumSet.allOfposto di Enum.values()? Chiedo perché valuesè una specie di metodo fantasma (non riesco a vedere la fonte in Enum.class) e non so quando è stato creato
Chirlo

1
@Chirlo C'è una domanda su questo. Sembra che Enum.values()sia più veloce se si prevede di iterare su di essi con un ciclo for migliorato (poiché restituisce un array), ma principalmente si tratta di stile e caso d'uso. Probabilmente è meglio usarlo EnumSet.allOf()se vuoi scrivere codice che esiste nella documentazione di Java invece che solo nelle specifiche, ma molte persone sembrano avere familiarità con lo stesso Enum.values().
4castle,

31

Citazione da JLS, sezione "Enum Body Declarations" :

Senza questa regola, il codice apparentemente ragionevole fallirebbe in fase di esecuzione a causa della circolarità di inizializzazione insita nei tipi enum. (Esiste una circolarità in ogni classe con un campo statico "auto-digitato".) Ecco un esempio del tipo di codice che fallirebbe:

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

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

L'inizializzazione statica di questo tipo enum genererebbe un'eccezione NullPointerException perché la variabile statica colorMap non è inizializzata quando vengono eseguiti i costruttori per le costanti enum. La restrizione sopra assicura che tale codice non venga compilato.

Tieni presente che l'esempio può essere facilmente modificato per funzionare correttamente:

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

La versione refactored è chiaramente corretta, poiché l'inizializzazione statica avviene dall'alto verso il basso.


9

forse questo è quello che vuoi

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

L'utilizzo Collections.unmodifiableMap()è un'ottima pratica qui. +1
4castle

Esattamente quello che stavo cercando. Mi piace anche vedere Collections.unmodifiableMap. Grazie!
LethalLima

6

Il problema è stato risolto tramite una classe annidata. Pro: è più corto e anche migliore dal consumo della CPU. Contro: un'altra classe nella memoria JVM.

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

Quando una classe viene caricata nella JVM, i campi statici vengono inizializzati nell'ordine in cui appaiono nel codice. Per es

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

L'output sarà 0. Notare che l'inizializzazione di test4 avviene in un processo di inizializzazione statico e durante questo tempo j non è ancora inizializzato come appare in seguito. Ora se cambiamo l'ordine degli inizializzatori statici in modo tale che j venga prima di test4. L'output sarà 6. Ma in caso di Enums non possiamo modificare l'ordine dei campi statici. La prima cosa in enum devono essere le costanti che in realtà sono istanze finali statiche di tipo enum. Pertanto, per enum è sempre garantito che i campi statici non vengano inizializzati prima delle costanti enum. Poiché non possiamo dare alcun valore sensibile ai campi statici da utilizzare nel costruttore enum , non avrebbe senso accedervi nel costruttore enum.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.