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

我正在探索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); } } 

正如预期的那样,main函数中的第一个语句将打印’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)

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

首先, enum只是一个具有常数字段的奇特class 。 也就是说,您声明的枚举常量实际上只是static字段。 所以

 enum SomeEnum { CONSTANT; } 

编译成类似的东西

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

其次, static字段按从左到右的顺序初始化,它们出现在源代码中。

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

在下面的

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

CONSTANT将首先初始化, CONSTANT_2秒。

第三, 当你访问其中一个常量(实际上只是一个static字段)时 , enum类型将被[初始化] [3] 。

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

如果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的引用。 所有RIVAL

当所有DOGS都被初始化时,执行返回

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

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

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

当您调用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

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

Interesting Posts