Enum of Enum - NULL

Я разрабатываю LALG-компилятор для моего курса в колледже на Java 1.6. Поэтому я сделал класс классов и класс грамматики.

EnumTypes

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

EnumGrammar

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.

Главная

public class Main {

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

}

Есть обходное решение или что-то в этом роде?

Спасибо!

Ответ 1

По сути, всегда очень рискованно позволять ссылку на объект, чтобы выйти за пределы класса до того, как класс будет полностью построен, то есть до завершения конструктора. Перечисления - это одиночные числа. Здесь у вас есть два класса, конструкторы которых получают друг друга в циклической зависимости. Добавьте к этому, что загрузка классов является ленивой, поэтому классы будут загружены, а экземпляры перечисления будут созданы, когда вы идете, и кажется вполне разумным, что результат концов зависит от порядка, в котором инициализируются перечисления.

Я не могу процитировать соответствующую точку из JLS прямо сейчас (я буду искать ее), но я считаю, что если вы разрешите ссылку на объект, чтобы "оставить класс" извне конструктора (что происходит здесь из-за того, что перечисления являются одиночными, инициализированными JVM), JVM может делать что-то странное.

EDIT: эти точки из JLS важны для случая:

  • 17.5.2 - A read of a final field of an object within the thread that constructs that object is ordered with respect to the initialization of that field within the constructor by the usual happens-before rules. If the read occurs after the field is set in the constructor, it sees the value the final field is assigned, otherwise it sees the default value. Поскольку значения enum внутренне обрабатываются как статические конечные поля (см. 16.5 ниже), если вы ссылаетесь на одно перечисление изнутри конструктора другого перечисления, конструктор которого ссылается на первый, хотя бы один из этих двух объектов еще не был полностью инициализирован, и поэтому эта ссылка может оставаться нулевой в этой точке.
  • 16.5 - The definite assignment/unassignment status of any construct within the class body of an enum constant is governed by the usual rules for classes
  • 8.3.2 - правила инициализации полей
  • 12.4.1 - при инициализации

Ответ 2

Вот что происходит, в порядке:

  • Ваши кодовые вызовы 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.ZERO - поле values уже установлено.

Ответ 3

Для обходного пути предположим, что у вас есть EnumA и EnumB, я просто поставлю имя EnumB в конструкторе EnumA.

Когда вам нужно извлечь EnumB из EnumA, вы можете просто использовать EnumB.valueOf(EnumA.this.enumB)

Например, Question is 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 с открытым исходным кодом для удовольствия, которое я только что написал прошлой ночью - Должен ли я уйти в отставку?