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] 。
第四,如果当前线程正在初始化类,则可以正常进行。
如果
C
的Class
对象指示当前线程正在为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.FELIX
。 CATS
目前正在初始化,所以我们继续。 CATS.FELIX
尚未分配值。 它目前正在建设中较低的堆栈。 所以它的值是null
。 所以GIN.RIVALS
获取null
的引用。 所有RIVAL
。
当所有DOGS
都被初始化时,执行返回
private static final CATS FELIX = new CATS(DOGS.AKAME);
其中DOGS.AKAME
现在指的是完全初始化的DOGS
对象。 它被分配到CATS#RIVAL
字段。 每个CATS
都相同。 换句话说,所有CATS
的RIVAL
字段都被分配了一个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
。
如果不清楚,请尝试使用在枚举元素的声明和构造函数中设置的断点来运行代码,以便更好地理解它。