2013-06-06 4 views
10

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

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

} 

Есть обходной путь или что-то подобное?

Спасибо!

+0

+1 и воспроизведение здесь: http://ideone.com/O9bZx3 –

+4

выглядит как головоломка круговой зависимости, связанной к static init из двух констант классов enum, ссылающихся друг на друга. –

+0

Это может происходить иногда независимо от круговой зависимости, особенно если ваша ленивая загрузка что-то или использование какого-то странного загрузчика классов. –

ответ

11

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

Я не могу процитировать соответствующую точку из 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. Поскольку значения перечислений внутренне рассматриваются как статические поля конечных (см 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 - при инициализации происходит
+0

IIRC Это становится еще более отвратительным, если вы используете многопоточный загрузчик классов Java 7: http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html. –

6

Вот что происходит, в следующем порядке:

  1. Ваш код вызывает EnumTypes.E.getEnumTiposValue(), вызывая класс загрузки EnumTypes.
  2. Начальная статическая инициализация EnumTypes - ее константы перечисления будут инициализированы в том порядке, в котором они объявлены.
  3. EnumTypes.A через EnumTypes.D инициализированы.
  4. EnumTypes.I начинает инициализацию - ссылки на его конструкторские ссылки EnumGrammar.THREE, запуск класса загрузки EnumGrammar.
  5. Начальная статическая инициализация EnumGrammar - ее константы перечисления будут инициализированы в том порядке, в котором они объявлены.
  6. EnumGrammar.ZERO инициализирован - ссылки на его конструкторские ссылки EnumTypes.A, EnumTypes.B, EnumTypes.F и EnumTypes.D. Из них EnumTypes.Fеще не был инициализирован. Поэтому ссылка на него - null.

Оттуда, статическая инициализация два классов перечислений заканчивается, но это не имеет значения для EnumGrammar.ZERO - его values поля уже установлено.

0

Для обходного пути предположим, что у вас есть EnumA и EnumB, я просто поставлю имя EnumB в конструкторе 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)), 
// ... 

и Ответ Энума

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 приложение для удовольствия я только что написал вчера вечером - Should I Resign?

Смежные вопросы