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

[url=http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.9]Java语言规范第三版8.9[/url]规定了enum里的构造器、初始化器和初始化块中不得引用该enum中非编译时常量的静态成员域。
[quote]It is a compile-time error to reference a static field of an enum type that is not a compile-time constant [url=http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#5313](§15.28)[/url] 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.[/quote]

规范特别指出构造器与初始化器禁止访问静态成员域是为了禁止一些循环初始化的状况。例子是:
[quote="Java Language Specification, 3rd"]
enum Color {
RED, GREEN, BLUE;
static final Map<String,Color> colorMap = new HashMap<String,Color>();
Color() {
colorMap.put(toString(), this);
}
}
[/quote]
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行,被注释掉的代码如果放进来就通不过编译,跟规范里提到的要避免的状况一样。但是把同样的逻辑放到了成员方法之后,我们就成功的看到了静态初始化错误:
[quote]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[/quote]
留意调用栈的状况。“... 1 more”没有显示出来的那个是Demo.main。它调用了PowerOfTwo枚举类型上的静态方法,引发了该类型的静态初始化(PowerOfTwo.<clinit>);其中,RED成员首先被初始化,调用构造器(PowerOfTwo.<init>);构造器则调用了成员方法registerValue来添加映射信息,访问到尚未被初始化到HashMap实例的静态成员域map,然后就出错了。

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

知道了问题没关系,只要问题有解决的办法就行。规范中也提供了Color例子的正确写法:
[quote="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);
}
}
[/quote]
关键点是在声明了静态成员域之后,在一个静态初始化块里来完成其内容的填充,而不要急着在构造器里就去做。当然要是在构造器里先判断一下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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值