【问题标题】:Java enum attributes returning null based on order of access基于访问顺序返回 null 的 Java 枚举属性
【发布时间】:2015-02-05 03:41:32
【问题描述】:

我正在探索 Java 中的枚举以了解它们如何被滥用,但我遇到了一种我无法解释的行为。考虑以下类:

public class PROGRAM {

public enum ENUM {;
    public enum ANIMALS {;
        public enum CATS {
            FELIX(DOGS.AKAME),
            GARFIELD(DOGS.WEED),
            BUBSY(DOGS.GIN);

            CATS(DOGS dog) {this.RIVAL = dog;}
            public DOGS RIVAL;
        }           
        public enum DOGS {
            GIN(CATS.FELIX), WEED(CATS.BUBSY), AKAME(CATS.GARFIELD);

            DOGS(CATS cat) {this.RIVAL = cat;}
            public CATS RIVAL;
        }
    }
}


public static void main(String[] args) {
    System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
    System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
}
}

主函数中的第一条语句将按预期打印“WEED”。第二个将打印'null'。但是,如果您切换它们,即

    System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
    System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);

第一条语句将打印“FELIX”,第二条语句现在将打印“null”。有没有人可以解释这种现象?

作为参考,我正在运行 Java(TM) SE 运行时环境(内部版本 1.8.0_05-b13)

【问题讨论】:

    标签: java enums


    【解决方案1】:

    这与枚举和类初始化有关。

    首先,enum 只是一个花哨的class,具有常量字段。 That is, the enum constants you declare are in reality just static fields.所以

    enum SomeEnum {
        CONSTANT;
    }
    

    编译成类似于

    final class SomeEnum extends Enum<SomeEnum> {
        public static final SomeEnum CONSTANT = new SomeEnum();
    }
    

    第二,static fields are initialized in the left to right order they appear in the source code.

    接下来,执行类变量初始化器和静态 类的初始值设定项,或接口的字段初始值设定项, 按照文字顺序,就好像它们是一个单独的块一样。

    在下面

    final class SomeEnum extends Enum<SomeEnum> {
        public static final SomeEnum CONSTANT = new SomeEnum();
        public static final SomeEnum CONSTANT_2 = new SomeEnum();
    }
    

    CONSTANT 将首先被初始化,CONSTANT_2 第二个。

    第三,an enum type will be [initialized][3] when you access one of its constants (which is really just a static field)

    第四,如果一个类当前正在被当前线程初始化,则正常进行。

    如果CClass 对象指示正在进行初始化 对于当前线程的C,那么这必须是一个递归请求 初始化。释放LC,正常完成。

    这一切是如何结合在一起的?

    这个

    ENUM.ANIMALS.CATS.GARFIELD.RIVAL
    

    被评估为

    CATS cat = ENUM.ANIMALS.CATS.GARFIELD;
    DOGS rvial = cat.RIVAL;
    

    第一次访问GARFIELD 会强制初始化enum 类型CATS。这开始初始化CATS 中的枚举常量。编译,那些看起来像

    private static final CATS FELIX = new CATS(DOGS.AKAME);
    private static final CATS GARFIELD = new CATS(DOGS.WEED);
    private static final CATS BUBSY = new CATS(DOGS.GIN);
    

    这些按顺序初始化。所以FELIX 是第一位的。作为其新实例创建表达式的一部分,它访问DOGS.AKAME,其中DOGS 类型尚未初始化,因此Java 开始对其进行初始化。 DOGS 枚举类型,编译后看起来像

    private static final DOGS GIN = new DOGS(CATS.FELIX);
    private static final DOGS WEED = new DOGS(CATS.BUBSY);
    private static final DOGS AKAME = new DOGS(CATS.GARFIELD);
    

    所以我们从GIN 开始。在其新的实例创建表达式中,它尝试访问CATS.FELIXCATS 正在初始化,所以我们继续。 CATS.FELIX 尚未被赋值。它目前正在堆栈中较低的位置进行构建。所以它的值为null。所以GIN.RIVALS 获得了对null 的引用。所有DOGS'RIVAL都会发生同样的情况。

    当所有DOGS都初始化完毕后,返回执行

    private static final CATS FELIX = new CATS(DOGS.AKAME);
    

    其中DOGS.AKAME 现在指的是完全初始化的DOGS 对象。这被分配给它的CATS#RIVAL 字段。每个CATS 都相同。换句话说,所有CATS'RIVAL 字段都被分配了一个DOGS 引用,而不是相反。

    对语句重新排序只是确定首先初始化哪个 enum 类型。

    【讨论】:

      【解决方案2】:

      当您调用ENUM.ANIMALS.CATS.GARFIELD.RIVAL 时,它将首先创建 CATS 枚举。在处理第一个元素 FELIX 时,它需要创建 DOGS 枚举,以便 DOGS.AKAME 可以作为参数传递给 CATS 构造函数。

      DOGS 构造函数接收 CATS 类型的参数,但由于 CATS 尚未初始化所有 CATS.something 将返回 null,因此将 DOGS 枚举中的所有元素的 RIVAL 属性设置为 null

      创建所有 DOGS 元素后,它会返回 CATS 并继续创建其元素,并将刚刚创建的 DOGS 元素作为参数传递。

      同样,当您颠倒调用顺序时,它首先创建 DOGS 枚举,这会导致 CATS 元素的 RIVAL 属性设置为 null

      如果不清楚,请尝试在枚举元素的声明和构造函数中设置断点运行代码以更好地理解它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-18
        • 2013-06-12
        • 1970-01-01
        • 2016-02-21
        • 2015-02-14
        • 2019-09-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多