web-dev-qa-db-fra.com

Pourquoi le constructeur d'énum ne peut-il pas accéder aux champs statiques?

Pourquoi le constructeur d'énum ne peut-il pas accéder aux champs et méthodes statiques? Ceci est parfaitement valide avec une classe, mais n'est pas autorisé avec une énumération.

Ce que j'essaie de faire, c'est de stocker mes instances d'énumération dans une carte statique. Considérez cet exemple de code qui permet la recherche par abréviation:

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

Cela ne fonctionnera pas car enum n'autorise pas les références statiques dans son constructeur. Il fonctionne cependant simplement s'il est implémenté en tant que 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
}
95
Steve Kuo

Le constructeur est appelé avant que les champs statiques aient tous été initialisés, car les champs statiques (y compris ceux représentant les valeurs d'énumération) sont initialisés dans l'ordre textuel et les valeurs d'énumération précèdent toujours les autres champs. Notez que dans votre exemple de classe, vous n'avez pas montré où ABBREV_MAP est initialisé - si c'est après DIMANCHE, vous obtiendrez une exception lorsque la classe sera initialisée.

Oui, c'est un peu pénible et aurait probablement pu être mieux conçu.

Cependant, la réponse habituelle dans mon expérience est d'avoir un static {} bloquez à la fin de tous les initialiseurs statiques et effectuez toutes les initialisations statiques à l'aide de EnumSet.allOf pour obtenir toutes les valeurs.

106
Jon Skeet

Citation de JLS, section "Enum Body Declarations" :

Sans cette règle, un code apparemment raisonnable échouerait au moment de l'exécution en raison de la circularité d'initialisation inhérente aux types enum. (Une circularité existe dans n'importe quelle classe avec un champ statique "auto-typé".) Voici un exemple du type de code qui échouerait:

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

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

L'initialisation statique de ce type d'énumération lèverait une NullPointerException car la variable statique colorMap n'est pas initialisée lorsque les constructeurs des constantes d'énumération s'exécutent. La restriction ci-dessus garantit que ce code ne sera pas compilé.

Notez que l'exemple peut facilement être refactorisé pour fonctionner correctement:

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 version refactorisée est clairement correcte, car l'initialisation statique se produit de haut en bas.

29
Phani

c'est peut-être ce que tu veux

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

Le problème a été résolu via une classe imbriquée. Avantages: il est plus court et également meilleur en termes de consommation CPU. Inconvénients: une classe de plus dans la mémoire 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);
    }
6
Pavel Vlasov

Lorsqu'une classe est chargée dans la JVM, les champs statiques sont initialisés dans l'ordre dans lequel ils apparaissent dans le code. Par exemple.

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

La sortie sera 0. Notez que l'initialisation de test4 a lieu dans le processus d'initialisation statique et pendant ce temps j n'est pas encore initialisé comme il apparaît plus tard. Maintenant, si nous changeons l'ordre des initialiseurs statiques de telle sorte que j précède test4. La sortie sera 6. Mais en cas d'énumérations, nous ne pouvons pas modifier l'ordre des champs statiques. La première chose dans enum doit être les constantes qui sont en fait des instances finales statiques de type enum. Ainsi, pour les enums, il est toujours garanti que les champs statiques ne seront pas initialisés avant les constantes enum. , cela n'aurait aucun sens d'y accéder dans le constructeur enum.

1
Hitesh