Почему конструктор enum не может получить доступ к статическим полям?
Почему конструктор 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 ответов
Конструктор вызывается до того, как все статические поля были инициализированы, поскольку статические поля (включая поля, представляющие значения перечисления) инициализируются в текстовом порядке, а значения перечисления всегда располагаются перед остальными полями. Обратите внимание, что в своем примере класса вы не показали, где инициализируется ABBREV_MAP - если это после SUNDAY, вы получите исключение при инициализации класса.
Да, это немного болезненно и, возможно, могло быть разработано лучше.
Тем не менее, мой обычный ответ - иметь static {}
заблокировать в конце всех статических инициализаторов и выполнить всю статическую инициализацию там, используя EnumSet.allOf
чтобы получить на все значения.
Цитата из 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); } }
Реорганизованная версия явно верна, так как статическая инициализация происходит сверху вниз.
Может это то что ты хочешь
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);
}
}
Задача решается через вложенный класс. Плюсы: он короче, а также лучше по потреблению процессора. Минусы: еще один класс в памяти 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);
}
Когда класс загружается в 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, было бы бессмысленно обращаться к ним в конструкторе enum.