Почему конструктор доступа не может пересылать статические поля?

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

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

Это не будет работать, поскольку перечисление не позволяет статические ссылки в его конструкторе. Однако он работает, только если он реализован как класс:

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
}

Ответы

Ответ 1

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

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

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

Ответ 2

Цитата из JLS, раздел "Заявления тела 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);
    }
}

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

Ответ 3

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

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

Ответ 4

Проблема решена через вложенный класс. Плюсы: он короче, а также лучше по сравнению с потреблением процессора. Минусы: еще один класс в 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);
    }

Ответ 5

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