English 中文(简体)
为什么枚举的构造函数无法访问静态字段?
原标题:
  • 时间:2009-01-14 17:45:30
  •  标签:

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

我想要做的是在静态 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);
    }
}

这将无法工作,因为枚举不允许在其构造函数中使用静态引用。但是,如果实现为类,则可以正常工作:

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之后,当类初始化时,您将获得一个异常。

是的,这有点麻烦,可能本来可以设计得更好些。

然而,在我的经验中通常的答案是,在所有静态初始化器的末尾使用 static {} 块,并在那里进行所有静态初始化,使用 EnumSet.allOf 获取所有值。

问题回答

引用自JLS,“枚举体声明”章节:

Without this rule, apparently reasonable code would fail at run time due to the initialization circularity inherent in enum types. (A circularity exists in any class with a "self-typed" static field.) Here is an example of the sort of code that would fail:

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 uninitialized when the constructors for the enum constants run. The restriction above ensures that such code won’t compile.

请注意,这个例子可以很容易地进行重构以使其正常工作:

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

问题通过嵌套类解决。优点:代码更短,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。但在 Enums 的情况下,我们不能更改静态字段的顺序。枚举中的第一件事必须是实际上是枚举类型的静态 final 实例的常量。因此,对于枚举,始终保证在枚举常量之前不会初始化静态字段。由于无法为用于枚举构造函数的静态字段提供任何明智的值,因此在枚举构造函数中访问它们是没有意义的。





相关问题
热门标签