`
RednaxelaFX
  • 浏览: 3052963 次
  • 性别: 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
分享到:
评论

相关推荐

    java中enum枚举的详细用法

    枚举类可以有构造函数,用于初始化枚举实例。例如: ```java public enum WeekDay { Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri("Friday"), Sat("Saturday"), Sun("Sunday"); ...

    Java中的Enum的使用与分析

    - **静态初始化**:枚举的元素在类加载时会被初始化。 - **单例模式**:每个枚举元素都是一个单例。 #### 三、枚举的内部结构 枚举类型在编译后会被转换成一个特殊的类。例如,上面的`Color`枚举类型在编译后会...

    Java Enum使用Demo源码

    在Java编程语言中,枚举(Enum)是一种特殊的类,用于定义固定的常量集合。它在许多场景下比常量接口或静态final变量更安全、更方便。本篇将通过"Java Enum使用Demo源码"深入探讨Java枚举的用法。 首先,枚举在Java...

    java 中enum的使用方法详解

    每个枚举值都是`WeekDays`类型的实例,它们的创建和初始化是自动完成的,不需要显式构造函数。 2. **枚举的遍历** 可以通过`values()`方法获取枚举的所有值并进行遍历,例如: ```java for (WeekDays day : ...

    Java中enum的用法.pdf

    Java中的枚举(enum)类型是在JDK 1.5版本引入的一种强大的特性,它用于定义一组相关的常量。在C/C++等早期语言中,枚举通常是一种基本的数据类型,而在Java中,枚举是一种类,具有更多的功能和灵活性。 1. **什么...

    java的Enum

    构造方法通常用于为枚举常量设置初始值或执行其他初始化操作。 **示例代码**: ```java enum Color { RED("红色"), GREEN("绿色"), BLUE("蓝色"); private String name; Color(String name) { this.name = ...

    java enum 枚举的spring boot2.x完美实现demo源码

    这里,`Status`枚举有两个实例,每个都有一个`description`属性,可以通过构造函数初始化,并提供了获取描述的方法。 在Spring Boot应用中,枚举可以作为控制器参数、服务方法返回值,甚至在数据库映射中使用。例如...

    java enum枚举教程学习资料

    如果需要,枚举元素可以带有初始化参数,这相当于在枚举类中定义了一个构造器来初始化元素: ```java public enum Color { RED("红色", 0), BLUE("蓝色", 1), BLACK("黑色", 2); private String name; ...

    javaenum学习.pdf

    以下是对`javaenum学习.pdf`中提到的Java枚举相关知识点的详细说明: 1. **枚举元素定义**: - 枚举元素的列表必须写在枚举类的开头,元素之间用逗号分隔。如果元素列表后面没有其他内容,可以不加分号;如果有...

    java 枚举(enum) 详解(学习资料)

    当编译器遇到枚举类型时,会自动为每个枚举常量创建一个私有的构造函数,并在类的静态初始化块中创建实例。这些实例存储在静态字段中,确保它们在类加载时创建,因此枚举对象是线程安全的。例如,反编译后的 `Color...

    详解Java的Enum的使用与分析

    通常,枚举值在编译时就已经初始化,通过`invokespecial`指令调用父类`Enum`的构造器完成实例化。 4. **枚举的方法**: - `values()`:返回枚举类的所有值的数组。 - `valueOf(String)`:根据字符串名称返回对应...

    Java 实例 - Enum(枚举)构造函数及方法的使用源代码-详细教程.zip

    枚举构造函数主要用于初始化枚举实例的内部状态,但需要注意,构造函数不能被外部直接调用,只能在枚举实例的定义中使用。例如: ```java public enum Color { RED(1), GREEN(2), BLUE(3); private int value; ...

    java中的单例模式

    - 实现方式通常是将Singleton类的实例作为静态成员变量,并在类加载时初始化。 ```java public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} ...

    动态初始化类+参数泛型化+统一返回值.zip

    在Java编程中,"动态初始化类"、"参数泛型化"和"统一返回值"是三个关键概念,它们在构建高效、灵活和可维护的软件系统中扮演着重要角色。接下来,我们将深入探讨这三个主题。 1. **动态初始化类**: 在传统的Java...

    java代码与编程题

    3. 初始化父类的非静态成员和非静态初始化块。 4. 调用父类的构造函数。 5. 初始化子类的非静态成员和非静态初始化块。 6. 调用子类的构造函数。 在给定的示例代码中,当执行`ChildClass cc = new ChildClass();`时...

    Java选择题武汉大学JAVA基础.pdf

    静态初始化块(static initializer block)、实例初始化块、构造方法(constructor)是类初始化时执行的代码块。 15. Java的泛型(Generics) 使用泛型可以创建可重用的组件,并提供类型安全,减少运行时的类型转换...

    javaAPI 5.0中文.rar,javaAPI 5.0中文.rar

    9. **枚举常量工厂方法**:在枚举中,可以定义工厂方法来创建枚举实例,提供了一种更加灵活的初始化方式。 10. **内省(Introspection)**:Java API提供了java.lang.reflect包,允许在运行时检查类、接口和对象的...

    JAVA类的特性资料

    9. **枚举类型(enum)**:Java中的枚举是一种特殊的类,用于定义一组固定的常量。枚举类型提供了更多的安全性和可读性,相比使用int常量或字符串常量更佳。 10. **异常处理**:Java通过try-catch-finally语句块...

    Java面试宝典

    - **静态初始化**:可以通过静态初始化块对静态成员进行初始化。 - **优先级**:静态成员的优先级高于非静态成员。当类中同时存在静态和非静态同名成员时,静态成员优先。 #### 11. 静态成员与非静态成员的关系 ...

Global site tag (gtag.js) - Google Analytics