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

时间:2015-02-05 03:41:32

标签: java enums

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

2 个答案:

答案 0 :(得分:2)

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

首先,enum只是一个带有常量字段的花哨classThat 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)

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

  

如果Class的{​​{1}}对象表示正在进行初始化   对于当前线程的C,那么这必须是递归请求   初始化。发布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尚未初始化,因此Java开始初始化它。编译的DOGS枚举类型看起来像

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); 开始。在其新实例创建表达式中,它尝试访问GINCATS.FELIX当前正在初始化,所以我们继续。 CATS尚未分配值。它目前正在建设中较低的堆栈。所以它的值是CATS.FELIX。因此null会引用GIN.RIVALS。所有null&#39;都会发生同样的情况。 DOGS

当初始化了所有RIVAL时,执行返回

DOGS

其中private static final CATS FELIX = new CATS(DOGS.AKAME); 现在指的是完全初始化DOGS.AKAME对象。它被分配到DOGS字段。每个CATS#RIVAL都相同。换句话说,所有CATS&#39; CATS字段被分配了RIVAL引用,但不是相反。

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

答案 1 :(得分:1)

当您致电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

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