为什么不能枚举的构造函数访问静态字段?
为什么不能枚举的构造函数访问静态字段和方法? 这对于一个类是完全有效的,但是对于枚举是不允许的。
我想要做的是将我的枚举实例存储在一个静态的地图。 考虑一下这个允许通过缩写查找的示例代码:
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 }
构造函数在静态字段全部被初始化之前被调用,因为静态字段(包括表示枚举值的那些字段)是按照文本顺序初始化的,枚举值总是在其他字段之前。 请注意,在您的类示例中,您尚未显示ABBREV_MAP的初始化位置 – 如果是在 SUNDAY 之后 ,则会在类初始化时收到exception。
是的,这有点痛苦,可能devise得更好。
然而,根据我的经验,通常的答案是在所有静态初始化器的末尾都有一个static {}
块,并且使用EnumSet.allOf
来获得所有的值。
来自JLS的引用,“Enum Body Declarations”一节 :
没有这个规则,由于枚举types固有的初始化循环性,显然合理的代码在运行时会失败。 (循环存在于具有“自我types”静态字段的任何类中)以下是将失败的代码types的示例:
enum Color { RED, GREEN, BLUE; static final Map<String,Color> colorMap = new HashMap<String,Color>(); Color() { colorMap.put(toString(), this); } } Static initialization of this enum type would throw a NullPointerException because the static variable colorMap is
当枚举常量的构造函数运行时未初始化。 上面的限制确保这些代码不会被编译。
请注意,该示例可以轻松重构以正常工作:
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); } } The refactored version is clearly correct, as static initialization occurs top to bottom.
也许这是你想要的
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); } }
该问题通过嵌套类解决。 优点: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); }
当一个类加载到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.但是在Enum的情况下,我们不能改变静态字段的顺序。 枚举中的第一件事必须是实际上是枚举types的静态最终实例的常量。因此对于枚举,它始终保证静态字段不会在枚举常量之前被初始化。因为我们不能给静态字段任何合理的值用于枚举构造函数,在枚举构造函数中访问它们将是没有意义的。