2013-06-06 3 views
10

Ich entwickle einen LALG-Compiler zu meinem College-Kurs auf Java 1.6. Also habe ich eine Klassen- und Grammatikklasse gemacht.Enum von Enum ist 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; 
    } 
} 

Als ich EnumTypes.E.getEnumTiposValue().getValues() nennen, wo angeblich der EnumTypes.F Wert sein, ist NULL.

Haupt

public class Main { 

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

} 

Es gibt eine Abhilfe oder so etwas?

Danke!

+0

+1 und Reproduktion ist hier: http://ideone.com/O9bZx3 –

+4

Sieht aus wie ein Puzzle zirkuläre Abhängigkeit zu dem im Zusammenhang static init der Konstanten der beiden enum-Klassen, die sich gegenseitig referenzieren. –

+0

Dies kann manchmal unabhängig von der zirkulären Abhängigkeit passieren, insbesondere wenn Sie etwas lahm legen oder einen seltsamen Klassenlader verwenden. –

Antwort

11

Grundsätzlich ist es immer eine sehr riskante Sache, einen Verweis auf ein Objekt außerhalb der Klasse zuzulassen, bevor die Klasse vollständig konstruiert ist, also bevor der Konstruktor fertig ist. Enums sind Singletons. Hier haben Sie zwei Klassen, deren Konstruktoren ihre Instanzen in einer zirkulären Abhängigkeit erhalten. Fügen Sie dazu hinzu, dass das Laden von Klassen faul ist, also werden die Klassen geladen und Enum-Instanzen erstellt, während Sie gehen, und es klingt ziemlich vernünftig, dass das Endergebnis von der Reihenfolge abhängt, in der die Enums initialisiert werden.

Ich kann nicht den entsprechenden Punkt von JLS jetzt zitieren (ich werde es suchen), aber ich glaube, dass, wenn Sie einen Verweis auf ein Objekt "verlassen die Klasse" von außerhalb des Konstruktors erlauben (die passiert hier, weil enums Singles sind, die von der JVM initialisiert wurden), die JVM ist frei, etwas Seltsames zu tun.

EDIT: diese Punkte von der JLS sind von Bedeutung für den Fall:

  • 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. Da ENUM-Werte intern wie static final Felder behandelt werden (16.5 siehe unten), wenn Sie eine Enum verweisen Innerhalb des Konstruktors einer anderen Enumeration, deren Konstruktor auf die erste Enumeration verweist, ist mindestens eines dieser beiden Objekte noch nicht vollständig initialisiert worden, so dass die Referenz an diesem Punkt möglicherweise noch null ist.
  • 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 - Regeln für die Initialisierung von Feldern
  • 12.4.1 - wenn Initialisierung erfolgt
+0

IIRC Es wird noch schwieriger, wenn Sie den Java 7 Multithread-Klassenlader verwenden: http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html. –

6

Hier ist, was passiert ist, um:

  1. Ihr Code ruft EnumTypes.E.getEnumTiposValue() auslöst Klassenladung von EnumTypes.
  2. Die statische Initialisierung von EnumTypes beginnt - seine Enum-Konstanten werden in der Reihenfolge initialisiert, in der sie deklariert sind.
  3. EnumTypes.A bis EnumTypes.D initialisiert werden.
  4. EnumTypes.I beginnt mit der Initialisierung - der Aufruf des Konstruktoraufrufs EnumGrammar.THREE löst das Laden der Klasse EnumGrammar aus.
  5. Die statische Initialisierung von EnumGrammar beginnt - seine Enum-Konstanten werden in der Reihenfolge ihrer Deklaration initialisiert.
  6. EnumGrammar.ZERO wird initialisiert - seine Konstruktoraufrufreferenzen , EnumTypes.B, EnumTypes.F und EnumTypes.D. Davon ist EnumTypes.Fnoch nicht initialisiert. Daher ist die Referenz darauf null.

Von dort statische Initialisierung der beiden Enum-Klassen beendet, aber es spielt keine Rolle für EnumGrammar.ZERO - seine values Feld bereits gesetzt worden ist.

0

Für die Problemumgehung, nehmen Sie an, dass Sie EnumA und EnumB haben, ich werde nur EnumBs Namen in EnumA-Konstruktor setzen.

Wenn Sie EnumB von Enuma abgerufen haben, können Sie einfach EnumB.valueOf (EnumA.this.enumB)

Zum Beispiel Frage ist die 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)), 
// ... 

und Antwort ist die 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; 
    } 

Jedes Mal, wenn ich brauche, um die nächste Frage aus einer Antwort zu bekommen

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

Dies sollte als Workaround einfach genug sein.

Beispiel Quelle: eine Open-Source-Android-App für Spaß, den ich gestern Abend gerade geschrieben - Should I Resign?