`
RednaxelaFX
  • 浏览: 3014871 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

Java中enum的静态成员的初始化

    博客分类:
  • Java
阅读更多
Java语言规范第三版8.9规定了enum里的构造器、初始化器和初始化块中不得引用该enum中非编译时常量的静态成员域。
引用
It is a compile-time error to reference a static field of an enum type that is not a compile-time constant (§15.28) from constructors, instance initializer blocks, or instance variable initializer expressions of that type. It is a compile-time error for the constructors, instance initializer blocks, or instance variable initializer expressions of an enum constant e to refer to itself or to an enum constant of the same type that is declared to the right of e.


规范特别指出构造器与初始化器禁止访问静态成员域是为了禁止一些循环初始化的状况。例子是:
Java Language Specification, 3rd 写道
enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();
    Color() {
        colorMap.put(toString(), this);
    }
}

Java枚举类型中的枚举成员是静态成员,它们会首先被静态初始化;其它成员都只能在枚举成员之后声明,如果通过初始化器(如上例)来初始化的话,则开始初始化RED时静态变量colorMap尚未被赋值。
初始化RED时要调用Color的构造器。如果允许构造器访问colorMap,就会对null调用了put()方法,于是遇到NullPointerException。

规范认为添加了上述限制后就可以让这种循环初始化的代码无法编译,从而杜绝其造成运行时异常的问题。

今天突然想起我前段时间才见到别人问过enum的初始化问题,而且就是遇到了静态初始化失败的错误。问了几个同学都说没问过我,稍微搜了下JavaEye问答频道也没看到。我还会是在哪里看到的呢,怪哉。我肯定是RP了……

想了会儿,总算构造出了记忆中见到的那种错误:
import java.util.*;

public class Demo {
    public static void main(String[] args) {
        PowerOfTwo i = PowerOfTwo.fromInt(2);
        System.out.println(i);
    }
}

enum PowerOfTwo {
    ONE(1), TWO(2), FOUR(4), EIGHT(8);
    
    private int value;
    
    PowerOfTwo(int value) {
        this.value = value;
        registerValue(); //map.put(value, this);
    }
    
    @Override
    public String toString() {
        return Integer.toString(this.value);
    }
    
    private void registerValue() {
        PowerOfTwo.map.put(value, this);
    }

    public static PowerOfTwo fromInt(int i) {
        return PowerOfTwo.map.get(i);
    }
    
    private static final Map<Integer, PowerOfTwo> map = new HashMap<Integer, PowerOfTwo>();
}

留意第17行,被注释掉的代码如果放进来就通不过编译,跟规范里提到的要避免的状况一样。但是把同样的逻辑放到了成员方法之后,我们就成功的看到了静态初始化错误:
引用
Exception in thread "main" java.lang.ExceptionInInitializerError
        at Demo.main(Demo.java:5)
Caused by: java.lang.NullPointerException
        at PowerOfTwo.registerValue(Demo.java:26)
        at PowerOfTwo.<init>(Demo.java:17)
        at PowerOfTwo.<clinit>(Demo.java:11)
        ... 1 more

留意调用栈的状况。“... 1 more”没有显示出来的那个是Demo.main。它调用了PowerOfTwo枚举类型上的静态方法,引发了该类型的静态初始化(PowerOfTwo.<clinit>);其中,RED成员首先被初始化,调用构造器(PowerOfTwo.<init>);构造器则调用了成员方法registerValue来添加映射信息,访问到尚未被初始化到HashMap实例的静态成员域map,然后就出错了。

也就是说上述限制的作用很有限……跟泛型有的一拼,呵呵。

知道了问题没关系,只要问题有解决的办法就行。规范中也提供了Color例子的正确写法:
Java Language Specification, 3rd 写道
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);
    }
}

关键点是在声明了静态成员域之后,在一个静态初始化块里来完成其内容的填充,而不要急着在构造器里就去做。当然要是在构造器里先判断一下null然后做合适的初始化也不是不行,但那样代码长了而且每构造一个实例都要检查一次,麻烦。原本需要针对每个实例做的初始化可以靠values()方法遍历所有的枚举成员来做。

回到PowerOfTwo的例子,那就是改成:
import java.util.*;

public class Demo {
    public static void main(String[] args) {
        PowerOfTwo i = PowerOfTwo.fromInt(2);
        System.out.println(i);
    }
}

enum PowerOfTwo {
    ONE(1), TWO(2), FOUR(4), EIGHT(8);
    
    private int value;
    
    PowerOfTwo(int value) {
        this.value = value;
    }
    
    @Override
    public String toString() {
        return Integer.toString(this.value);
    }

    public static PowerOfTwo fromInt(int i) {
        return PowerOfTwo.map.get(i);
    }
    
    private static Map<Integer, PowerOfTwo> map = new HashMap<Integer, PowerOfTwo>();
    static {
        for (PowerOfTwo p : PowerOfTwo.values()) {
            PowerOfTwo.map.put(p.value, p);
        }
    }
}


Effective Java, 2nd的Item 33有关于嵌套枚举类型的初始化的例子。

这个例子教育我们写Java代码的时候顺序要注意清楚了,不然一个不小心就……||||
好吧我不是不写Java代码或者不写Java帖的。虽然现在用得不多,我还是得保持最低限度的熟练才行 T.T
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics