Почему конструктор enum не может получить доступ к статическим полям? - PullRequest
93 голосов
/ 14 января 2009

Почему конструктор enum не может получить доступ к статическим полям и методам? Это совершенно верно для класса, но не допускается для перечисления.

Я пытаюсь сохранить экземпляры 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
}

Ответы [ 5 ]

105 голосов
/ 14 января 2009

Конструктор вызывается до того, как все статические поля были инициализированы, потому что статические поля (включая поля, представляющие значения перечисления) инициализируются в текстовом порядке, а значения перечисления всегда идут перед другими полями. Обратите внимание, что в своем примере класса вы не показали, где инициализируется ABBREV_MAP - если он после Воскресенье, вы получите исключение при инициализации класса.

Да, это немного болезненно и, возможно, могло бы быть лучше.

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

29 голосов
/ 26 октября 2011

Цитата из JLS, раздел "Объявления тела Enum" :

Без этого правила очевидно разумный код потерпит неудачу во время выполнения из-за цикличности инициализации, присущей типам enum. (A цикличность существует в любом классе со статическим полем "с типизацией".) Вот пример кода, который потерпит неудачу:

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

Реорганизованная версия явно верна, так как статическая инициализация происходит сверху вниз.

7 голосов
/ 18 февраля 2016

возможно это то, что вы хотите

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);
    }
}
6 голосов
/ 10 февраля 2016

Проблема решена с помощью вложенного класса. Плюсы: он короче, а также лучше по потреблению процессора. Минусы: еще один класс в памяти 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 голос
/ 02 мая 2014

Когда класс загружается в 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. Таким образом, для перечислений всегда гарантируется, что статические поля не будут инициализированы перед константами enum. , было бы бессмысленно обращаться к ним в конструкторе enum.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...