Java 枚举属性根据访问顺序返回 null

Java enum attributes returning null based on order of access

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

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

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

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

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

作为参考,我是 运行 Java(TM) SE 运行时环境(build 1.8.0_05-b13)

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

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

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

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

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

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

首先,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.

Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.

在下面

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)

第四,如果一个class当前正在被当前线程初始化,你可以正常进行。

If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.

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

这个

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全部初始化完毕后,执行returns到

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

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

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