2013-06-06 13 views
10

Opracowuję kompilator LALG do mojego kursu kolegium na Java 1.6. Zrobiłem więc klasę typów i gramatykę.Enum of Enum jest 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; 
    } 
} 

Kiedy zadzwonić EnumTypes.E.getEnumTiposValue().getValues() gdzie mają być wartość EnumTypes.F jest NULL.

główna

public class Main { 

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

} 

Istnieje obejście lub coś w tym stylu?

Dzięki!

+0

+1 i reprodukcja jest tutaj: http://ideone.com/O9bZx3 –

+4

Wygląda okrągłym zależności układanki związanej z tą static init z dwóch stałych klas enum, które się nawzajem odwołują. –

+0

Może się to zdarzyć czasami niezależnie od zależności cyklicznej, jeśli leniwy ładujesz coś lub używasz jakiegoś dziwnego programu ładującego klasy. –

Odpowiedz

11

Zasadniczo, zawsze jest bardzo ryzykowne, aby umożliwić odniesienie do obiektu, aby wyjść poza klasę, zanim klasa zostanie w pełni skonstruowana, czyli przed ukończeniem konstruktora. Enums to single. Tutaj masz dwie klasy, których konstruktory odbierają nawzajem swoje wystąpienia w zależności cyklicznej. Dodajmy do tego, że ładowanie klasy jest leniwy, więc klasy zostaną załadowane, a instancje wyliczeniowe tworzone w trakcie pracy i wydaje się całkiem rozsądne, że wynik końcowy zależy od kolejności, w której inicjowane są wyliczenia.

Nie mogę zacytować odpowiedniego punktu z JLS teraz (będę go szukać), ale wierzę, że jeśli zezwolisz na odniesienie do obiektu, aby "opuścić klasę" spoza konstruktora (który dzieje się tutaj, ponieważ konwertery są inicjowane przez JVM), JVM może zrobić coś dziwnego.

EDIT: te punkty z JLS mają znaczenie dla postępowania:

  • 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. Od wartości wyliczeniowe są wewnętrznie traktowane jak statycznych pól końcowych (patrz 16.5 poniżej), jeśli odwołać jeden enum od wewnątrz konstruktora innego wyliczenia, którego konstruktor odwołuje się do pierwszego, co najmniej jeden z tych dwóch obiektów nie zostanie jeszcze w pełni zainicjowany, a więc odwołanie może w tym punkcie nadal mieć wartość zerową.
  • 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 - zasady inicjalizacji pól
  • 12.4.1 - kiedy następuje inicjalizacja
+0

IIRC Robi się jeszcze grubsza, jeśli używasz wielowątkowego programu ładującego klasy Java 7: http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html. –

6

Oto co się dzieje, w kolejności:

  1. Kod nazywa EnumTypes.E.getEnumTiposValue(), wywołując ładowanie klasy: EnumTypes.
  2. Rozpocznie się inicjalizacja statyczna EnumTypes - jego stałe wyliczeniowe zostaną zainicjowane w kolejności, w jakiej zostały zadeklarowane.
  3. EnumTypes.A do EnumTypes.D zostały zainicjalizowane.
  4. EnumTypes.I rozpoczyna inicjalizację - referencje wywołania konstruktora EnumGrammar.THREE, wyzwalające ładowanie klasy EnumGrammar.
  5. Rozpoczyna się inicjalizacja statyczna EnumGrammar - jego stałe wyliczeniowe zostaną zainicjowane w kolejności, w jakiej zostały zadeklarowane.
  6. EnumGrammar.ZERO zostało zainicjowane - referencje wywołania konstruktora: EnumTypes.A, EnumTypes.B, EnumTypes.F i EnumTypes.D. Spośród nich, EnumTypes.Fnie został jeszcze zainicjowany. Dlatego odniesienie do niego to null.

Stamtąd statyczna inicjalizacja dwóch klas enum kończy, ale to nie ma znaczenia dla EnumGrammar.ZERO - jego values pole zostało już ustawione.

0

Aby obejść ten problem, załóżmy, że masz EnumA i EnumB, po prostu wstawię nazwę EnumB do konstruktora EnumA.

Kiedy trzeba pobierać EnumB z Enuma, można po prostu użyć EnumB.valueOf (EnumA.this.enumB)

Na przykład, pytanie jest 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)), 
// ... 

i odpowiedzi jest Enûma

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

Ilekroć muszę się następne pytanie od Odpowiedź

public Question getNextQuestion() { 
    if (nextQuestionName == null) { 
     return null; 
    } 
    return Question.valueOf(nextQuestionName); 
} 

Powinno to być wystarczająco proste, aby można było obejść ten problem.

Przykład źródło: open source Android aplikacja dla zabawy po prostu napisane w nocy - Should I Resign?