Java 枚举的枚举为空

Java 枚举的枚举为空,java,reference,enums,null,Java,Reference,Enums,Null,我正在为我的大学课程Java 1.6开发一个LALG编译器。所以我上了一堂类型课和语法课 枚举类型 public enum EnumTypes { A("OLA"), B("MUNDO"), C("HELLO"), D("WORLD"), /** * The order below is reversed on purpose. * Revert it and will you get a NULL list of types fu

我正在为我的大学课程Java 1.6开发一个LALG编译器。所以我上了一堂类型课和语法课

枚举类型

public enum EnumTypes {

    A("OLA"),
    B("MUNDO"),
    C("HELLO"),
    D("WORLD"),

    /**
     * The order below is reversed on purpose.
     * Revert it and will you get a NULL list of types furder.
     */

    I(EnumGrammar.THREE),
    H(EnumGrammar.TWO),
    F(EnumGrammar.ONE),
    E(EnumGrammar.ZERO);

    private String strValue;
    private EnumGrammar enumGrammarValue;

    private EnumTypes(String strValue) {
        this.strValue = strValue;
    }

    private EnumTypes(EnumGrammar enumGrammarValue) {
        this.enumGrammarValue = enumGrammarValue;
    }

    public String getStrValue() {
        return strValue;
    }

    public EnumGrammar getEnumTiposValue() {
        return enumGrammarValue;
    }
}
枚举语法

public enum EnumGrammar {

    ZERO(EnumTypes.A,EnumTypes.B,EnumTypes.F,EnumTypes.D),
    ONE(EnumTypes.C),
    TWO(EnumTypes.B,EnumTypes.H),
    THREE(EnumTypes.D,EnumTypes.A,EnumTypes.C);

    private EnumTypes[] values;

    private EnumGrammar(EnumTypes ... values) {
        this.values = values;
    }

    public EnumTypes[] getValues() {
        return values;
    }
}
当我调用
EnumTypes.E.getEnumTiposValue().getValues()
时,其中应该是
EnumTypes.F
值为NULL

Main

public class Main {

    public static void main(String[] args) {
        //prints [A, B, null, D]
        System.out.println(Arrays.toString(EnumTypes.E.getEnumTiposValue().getValues()));
    }

}
有解决办法吗


谢谢

从本质上讲,在类完全构造之前,也就是在构造函数完成之前,允许对对象的引用进入类之外总是一件非常危险的事情。枚举是单例的。这里有两个类,它们的构造函数以循环依赖关系接收彼此的实例。再加上类加载是延迟的,所以类将被加载,枚举实例将在运行时创建,最终结果取决于枚举初始化的顺序,这听起来很合理

我现在无法引用JLS中的对应点(我会查找),但我相信如果您允许对对象的引用从构造函数外部“离开类”(由于枚举是由JVM初始化的单例),JVM可以自由地执行一些奇怪的操作

编辑:JLS中的这些要点对于本案例非常重要:

  • -
    在构造该对象的线程中读取该对象的最后一个字段,按照通常的before规则对构造函数中该字段的初始化进行排序。如果在构造函数中设置字段后进行读取,则会看到最终字段分配的值,否则会看到默认值。
    由于枚举值在内部被视为静态最终字段(请参见下面的16.5),如果您从另一个枚举的构造函数内部引用一个枚举,而该枚举的构造函数引用了第一个枚举,这两个对象中至少有一个尚未完全初始化,因此此时引用可能仍然为null
  • -
    枚举常量的类体中任何构造的明确赋值/取消赋值状态受类的常规规则管辖
  • -字段初始化规则
  • -初始化发生时

以下是正在发生的事情,顺序如下:

  • 您的代码调用
    EnumTypes.E.getEnumTiposValue()
    ,触发
    EnumTypes
    的类加载
  • EnumTypes
    的静态初始化开始-其枚举常量将按照声明的顺序初始化
  • EnumTypes.A
    EnumTypes.D
    被初始化
  • EnumTypes.I
    开始初始化-其构造函数调用引用了
    EnumGrammar.THREE
    ,触发类加载
    EnumGrammar
  • EnumGrammar
    的静态初始化开始-其枚举常量将按声明顺序初始化
  • EnumGrammar.ZERO
    已初始化-其构造函数调用引用了
    EnumTypes.A
    EnumTypes.B
    EnumTypes.F
    EnumTypes.D
    。其中,
    EnumTypes.F
    尚未初始化。因此,对它的引用是
    null

  • 从那时起,两个enum类的静态初始化完成,但这对于EnumGrammar并不重要。零-它的
    字段已经设置。

    对于解决方法,假设您有EnumA和EnumB,我将把EnumB的名称放在EnumA的构造函数中

    当您必须从EnumA检索EnumB时,只需使用EnumB.valueOf(EnumA.this.EnumB)

    例如,问题是EnumB

    public enum Question {
    RICH_ENOUGH(R.string.question_rich_enough, Arrays.asList(Answer.RICH_ENOUGH_YES, Answer.RICH_ENOUGH_NO)),
    ARE_YOU_SURE(R.string.question_are_you_sure, Arrays.asList(Answer.ARE_YOU_SURE_YES, Answer.ARE_YOU_SURE_NO)),
    FOUND_A_NEW_JOB(R.string.question_found_new_job, Arrays.asList(Answer.FOUND_A_NEW_JOB_YES, Answer.FOUND_A_NEW_JOB_NO)),
    // ...
    
    答案是EnumA

    public enum Answer {
        RICH_ENOUGH_YES(R.string.answer_yes, "ARE_YOU_SURE"),
        RICH_ENOUGH_NO(R.string.answer_no, "THAT_SOMEBODY"),
        ARE_YOU_SURE_YES(R.string.answer_yes, null),
        ARE_YOU_SURE_NO(R.string.answer_no, "FOUND_A_NEW_JOB"),
        FOUND_A_NEW_JOB_YES(R.string.answer_yes, "GO_FOR_NEW_JOB"),
        // ...
    
        private final int answerStringRes;
        // Circular reference makes nulls
        private final String nextQuestionName;
    
        Answer(@StringRes int answerStringRes, String nexQuestionName) {
            this.answerStringRes = answerStringRes;
            this.nextQuestionName = nexQuestionName;
        }
    
    每当我需要从答案中得到下一个问题时

    public Question getNextQuestion() {
        if (nextQuestionName == null) {
            return null;
        }
        return Question.valueOf(nextQuestionName);
    }
    
    这应该足够简单,可以作为一种变通方法


    示例源代码:我昨晚刚刚编写的一个开源Android趣味应用程序-

    +1,复制如下:看起来像是一个循环依赖性谜题,与两个枚举类相互引用的常量的静态init有关。如果您懒散地加载某些内容或使用一些奇怪的类加载器。如果您使用Java 7多线程类加载器,可能会重复IIRC,它会变得更糟糕:。