为什么 enum 的构造函数不能访问静态字段?

为什么 enum 的构造函数不能访问静态字段和方法?这对类是完全有效的,但对枚举是不允许的。

我要做的是将我的枚举实例存储在一个静态 Map 中。考虑一下这个示例代码,它允许按缩写进行查找:

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

由于枚举在其构造函数中不允许静态引用,因此这将不起作用。然而,如果它作为一个类实现,那么只需 find 就可以工作:

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
}
47530 次浏览

The constructor is called before the static fields have all been initialized, because the static fields (including those representing the enum values) are initialized in textual order, and the enum values always come before the other fields. Note that in your class example you haven't shown where ABBREV_MAP is initialized - if it's 之后 SUNDAY, you'll get an exception when the class is initialized.

是的,这是一个有点痛苦,也许可以设计得更好。

However, the usual answer in my experience is to have a static {} block at the end of all the static initializers, and do all static initialization there, using EnumSet.allOf to get at all the values.

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

重构版本显然是正确的,因为静态初始化是从上到下进行的。

当一个类被加载到 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 的情况下,我们不能改变静态字段的顺序。枚举中的第一件事必须是实际上是枚举类型的静态最终实例的常量。因此,对于枚举,它始终保证静态字段不会在枚举常数之前初始化。因为我们不能为枚举构造函数中使用的静态字段提供任何有意义的值,所以在枚举构造函数中访问它们是没有意义的。

该问题通过嵌套类解决。优点: 它更短,而且根据 CPU 消耗情况也更好。缺点: 在 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);
}

也许这就是你想要的

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