Чому конструктор enum не може отримати доступ до статичних полів?


110

Чому конструктор enum не може отримати доступ до статичних полів і методів? Це цілком справедливо для класу, але не допускається із перерахунком.

Що я намагаюся зробити - це зберігати мої екземпляри перерахунків у статичній карті. Розглянемо цей приклад коду, який дозволяє шукати за допомогою скорочення:

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 - якщо це після СУНДИ, ви отримаєте виняток, коли клас ініціалізується.

Так, це трохи болить і, мабуть, було б спроектовано краще.

Однак звичайна відповідь на мій досвід - мати static {}блок в кінці всіх статичних ініціалізаторів і робити всю статичну ініціалізацію там, використовуючи EnumSet.allOf для отримання всіх значень.


40
Якщо ви додасте вкладений клас, то статистика цього буде ініціалізована у відповідний час.
Том Хотін - тайклін

О, приємний. Я не думав про це.
Джон Скіт

3
Біт непарного, але якщо ви викликаєте статичний метод у конструкторі enum, який повертає статичне значення, він буде складати чудово - але значення, яке він повертає, буде типовим для цього типу (тобто 0, 0,0, '\ u0000' або null), навіть якщо ви явно встановили його (якщо це не оголошено як final). Здогадайтесь, що це буде важко зловити!
Марк Родос

2
запитання швидкого відключення @JonSkeet: Чи є якась причина, яку ви використовуєте EnumSet.allOfзамість Enum.values()? Я запитую, тому що valuesце свого роду фантомний метод (не можу побачити джерело Enum.class), і я не знаю, коли його створили
Chirlo

1
@Chirlo Про це є питання . Здається, Enum.values()це швидше, якщо ви плануєте ітерацію над ними з розширеним циклом (оскільки він повертає масив), але в основному його стосується стилю та випадку використання. Це, мабуть, краще використовувати, EnumSet.allOf()якщо ви хочете написати код, який існує в документації Java, а не просто в специфікаціях, але багато людей, здається, Enum.values()все одно знайомі .
4касл

31

Цитата з JLS, розділ "Декларації тіла Enum" :

Без цього правила, очевидно, розумний код не зможе виконати час виконання через циклічність ініціалізації, властиву типам enum. (Циркулярність існує в будь-якому класі зі "самонаписаним" статичним полем.) Ось приклад типу коду, який не вдасться:

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
4castle

Саме те, що я шукав. Мені також подобається бачити Collections.unmodifiableMap. Дякую!
ЛетальнаЛіма

6

Проблема вирішена за допомогою вкладеного класу. Плюси: це коротше, а також краще за рахунок споживання процесора. Мінуси: ще один клас пам'яті 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

Коли клас завантажується в 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.Але у випадку Enums ми не можемо змінити порядок статичних полів. Перше, що в enum повинні бути константами, які є фактично статичними кінцевими екземплярами enum type.Thus для enums завжди гарантують, що статичні поля не будуть ініціалізовані перед перерахунками. , не матиме сенсу звертатися до них у конструкторі enum.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.