为啥枚举的构造函数不能访问静态字段?

Posted

技术标签:

【中文标题】为啥枚举的构造函数不能访问静态字段?【英文标题】:Why can't enum's constructor access static fields?为什么枚举的构造函数不能访问静态字段? 【发布时间】:2010-10-01 10:25:06 【问题描述】:

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

我想要做的是将我的枚举实例存储在静态地图中。考虑这个允许通过缩写查找的示例代码:

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

【问题讨论】:

正如***.com/questions/443980/… 所描述的,我们有这个解决方法 Day(String...abbreviations) For.ABBR.put(name(), this); for (String abbr : abbreviations) For.ABBR.put(abbr, this); // 解决方法 ***.com/questions/443980/… private static class For private static final Map ABBR = new HashMap(); 【参考方案1】:

在静态字段全部初始化之前调用构造函数,因为静态字段(包括表示枚举值的那些)按文本顺序初始化,并且枚举值总是在其他字段之前。请注意,在您的类示例中,您没有显示 ABBREV_MAP 的初始化位置 - 如果它是 SUNDAY 之后,则在初始化类时会出现异常。

是的,这有点麻烦,而且可能设计得更好。

但是,根据我的经验,通常的答案是在所有静态初始化程序的末尾有一个 static 块,并在那里进行所有静态初始化,使用 EnumSet.allOf 获取所有值。

【讨论】:

如果你添加一个嵌套类,那么它的静态属性会在适当的时候被初始化。 哦,不错。我没想到。 有点奇怪,但如果你在枚举构造函数中调用一个静态方法,它返回一个静态值,它将编译得很好——但它返回的值将是该类型的默认值(即 0 , 0.0, '\u0000' 或 null),即使您明确设置它(除非它被声明为 final)。猜猜这将是一个难以捉摸的! 快速衍生问题@JonSkeet:有什么理由使用EnumSet.allOf 而不是Enum.values()?我问是因为values 是一种幻象方法(在Enum.class 中看不到源代码),我不知道它是什么时候创建的 @Chirlo 有一个question 与此有关。如果您计划使用增强的 for 循环(因为它返回一个数组)对它们进行迭代,Enum.values() 似乎更快,但主要是关于样式和用例。如果您想编写存在于 Java 文档中的代码而不是规范中的代码,使用 EnumSet.allOf() 可能会更好,但无论如何,很多人似乎都熟悉 Enum.values()【参考方案2】:

引用JLS, section "Enum Body Declarations":

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

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

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

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

问题通过嵌套类解决。优点:它更短,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);
    

【讨论】:

【参考方案5】:

也许这就是你想要的

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

【讨论】:

在这里使用Collections.unmodifiableMap() 是一个非常好的做法。 +1 正是我想要的。我也喜欢看 Collections.unmodifiableMap。谢谢!

以上是关于为啥枚举的构造函数不能访问静态字段?的主要内容,如果未能解决你的问题,请参考以下文章

C++:为啥我的 DerivedClass 的构造函数无法访问 BaseClass 的受保护字段?

C# 静态构造函数

如果构造函数在私有部分,为啥我们不能创建对象?

简单介绍如何使用PowerMock和Mockito来mock 1. 构造函数 2. 静态函数 3. 枚举实现的单例 4. 选择参数值做为函数的返回值(转)

构造函数

为啥枚举可以有包私有构造函数?