为什么枚举的构造函数无法访问静态字段?

2022-08-31 08:49:02

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

我正在尝试做的是将我的枚举实例存储在静态映射中。请考虑以下允许通过缩写进行查找的示例代码:

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初始化的位置 - 如果是在星期日之后,则在初始化类时将出现异常。

是的,这有点痛苦,可能可以设计得更好。

但是,根据我的经验,通常的答案是在所有静态初始值设定项的末尾有一个块,并在那里执行所有静态初始化,用于获取所有值。static {}EnumSet.allOf


答案 2

引自 JLS,“枚举主体声明”部分

如果没有此规则,由于枚举类型固有的初始化循环性,显然合理的代码在运行时会失败。(循环性存在于具有“自类型”静态字段的任何类中。下面是一个失败的代码示例:

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

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