2009-10-01 8 views
29

Existe-t-il un moyen de contourner les problèmes de chargement de classes provoqués par l'existence de deux énumérations se référant mutuellement?Java Enums: deux types enum, contenant chacun des références les uns aux autres?

J'ai deux ensembles de énumérations, Foo et Bar, définis comme ceci:

public class EnumTest { 

    public enum Foo { 
    A(Bar.Alpha), 
    B(Bar.Delta), 
    C(Bar.Alpha); 

    private Foo(Bar b) { 
     this.b = b; 
    } 

    public final Bar b; 
    } 

    public enum Bar { 
    Alpha(Foo.A), 
    Beta(Foo.C), 
    Delta(Foo.C); 

    private Bar(Foo f) { 
     this.f = f; 
    } 

    public final Foo f; 
    } 

    public static void main (String[] args) { 
    for (Foo f: Foo.values()) { 
     System.out.println(f + " bar " + f.b); 
    } 
    for (Bar b: Bar.values()) { 
     System.out.println(b + " foo " + b.f); 
    } 
    } 
} 

Le code ci-dessus produit en sortie:

A bar Alpha 
B bar Delta 
C bar Alpha 
Alpha foo null 
Beta foo null 
Delta foo null 

Je comprends pourquoi cela se produit - la machine virtuelle démarre classloading Foo; il voit le constructeur Bar.Alpha dans Foo.A, donc il commence à classer Bar. Il voit la référence Foo.A dans l'appel au constructeur de Bar.Alpha, mais (puisque nous sommes toujours dans le constructeur de Foo.A) Foo.A est nul à ce stade, donc le constructeur de Bar.Alpha reçoit un null. Si j'inverse les deux pour les boucles (ou si je fais référence à Bar avant Foo), la sortie change pour que les valeurs de Bar soient correctes, mais pas celles de Foo.

Y at-il un moyen de contourner ce problème? Je sais que je peux créer une carte statique et une carte statique dans une 3ème classe, mais cela me semble assez hackish. Je pourrais aussi faire des méthodes Foo.getBar() et Bar.getFoo() qui se réfèrent à la carte externe, donc ça ne changerait même pas mon interface (les classes que j'ai utilisées inspecteurs au lieu des champs publics), mais ça sent toujours sorte d'impur pour moi.

(La raison pour laquelle je fais cela dans mon système actuel: Foo et Bar représentent les types de messages que 2 applications envoient les uns aux autres, les champs Foo.b et Bar.f représentent le type de réponse attendu pour un message donné - donc dans mon exemple de code, quand app_1 reçoit un Foo.A, il doit répondre avec un Bar.Alpha et vice-versa.)

Merci d'avance!

Répondre

21

L'une des meilleures façons serait d'utiliser la technique du polymorphisme enum:

public class EnumTest { 
    public enum Foo { 
     A { 

      @Override 
      public Bar getBar() { 
       return Bar.Alpha; 
      } 
     }, 
     B { 

      @Override 
      public Bar getBar() { 
       return Bar.Delta; 
      } 
     }, 
     C { 

      @Override 
      public Bar getBar() { 
       return Bar.Alpha; 
      } 
     }, 

     ; 

     public abstract Bar getBar(); 
    } 

    public enum Bar { 
     Alpha { 

      @Override 
      public Foo getFoo() { 
       return Foo.A; 
      } 
     }, 
     Beta { 

      @Override 
      public Foo getFoo() { 
       return Foo.C; 
      } 
     }, 
     Delta { 

      @Override 
      public Foo getFoo() { 
       return Foo.C; 
      } 
     }, 

     ; 

     public abstract Foo getFoo(); 
    } 

    public static void main(String[] args) { 
     for (Foo f : Foo.values()) { 
      System.out.println(f + " bar " + f.getBar()); 
     } 
     for (Bar b : Bar.values()) { 
      System.out.println(b + " foo " + b.getFoo()); 
     } 
    } 
} 

Le code ci-dessus produit la sortie que vous voulez:

A bar Alpha 
B bar Delta 
C bar Alpha 
Alpha foo A 
Beta foo C 
Delta foo C 

Voir aussi:

+1

Semble à moi comme sur complication. réponse @ weiji est beaucoup plus propre, IMO. Pourquoi cette approche est-elle meilleure (vous avez dit "le meilleur")? –

+3

@NoamNelke J'ai déjà voté pour l'approche de weiji, ce qui est très intéressant. Bien que je pense personnellement que c'est beaucoup mieux comme je l'ai recommandé car la référence cyclique est dans son énumération, une grande différence entre nos réponses est que la mienne vous permettrait également de faire une logique d'exécution avant de revenir, par exemple: 'public Bar getBar (booléen nullIfAlpha) {return nullIfAlpha? null: Bar.Alpha; } '. Quoi qu'il en soit, j'ai modifié ma réponse à "l'un des meilleurs", car il peut être basé sur l'opinion. Merci pour votre réponse! – falsarella

10

Le problème n'est pas tellement "deux enums se réfèrent l'un à l'autre", mais plutôt "deux enums se référencent mutuellement dans leurs constructeurs". Cette référence circulaire est la partie délicate. Comment utiliser les méthodes Foo.setResponse(Bar b) et Bar.setResponse(Foo f)? Au lieu de définir une barre de Foo dans le constructeur Foo (et de la même manière un Foo dans le constructeur Bar), vous faites l'initialisation en utilisant une méthode? .:
par exemple

Foo:

public enum Foo { 
    A, B, C; 

    private void setResponse(Bar b) { 
    this.b = b; 
    } 

    private Bar b; 

    public Bar getB() { 
    return b; 
    } 

    static { 
    A.setResponse(Bar.Alpha); 
    B.setResponse(Bar.Delta); 
    C.setResponse(Bar.Alpha); 
    } 
} 

Bar:

public enum Bar { 
    Alpha, Beta, Delta; 

    private void setResponse(Foo f) { 
    this.f = f; 
    } 

    private Foo f; 

    public Foo getF() { 
    return f; 
    } 

    static { 
    Alpha.setResponse(Foo.A); 
    Beta.setResponse(Foo.C); 
    Delta.setResponse(Foo.C); 
    } 
} 

, vous mentionnez également que Foo et Bar sont deux types de messages. Serait-il possible de les combiner en un seul type? D'après ce que je peux voir, leur comportement ici est le même. Cela ne résout pas la logique circulaire, mais il peut vous donner une autre idée de votre conception ...

+0

Hey, je suis [edited votre réponse] (http://stackoverflow.com/posts/1506635/revisions) pour fixer et améliorer. Veuillez le modifier à nouveau ou le restaurer si vous n'êtes pas d'accord. – falsarella

3

Comme il semble que vous allez être coder en dur de toute façon, pourquoi ne pas avoir quelque chose comme

public static Bar responseBar(Foo f) { 
switch(f) { 
    case A: return Bar.Alpha; 
    // ... etc 
} 
} 

pour chaque énumération? Il semble que vous ayez des réponses qui se chevauchent dans votre exemple, de sorte que vous puissiez même tirer parti des cas qui surviennent.

EDIT:

J'aime la suggestion de Tom du EnumMap; I pense performance est probablement plus rapide sur EnumMap, mais le type de construction élégante décrit dans Effective Java ne semble pas être offert par ce problème particulier - cependant, la solution de commutateur proposée ci-dessus serait un bon moyen de construire deux EnumMaps, alors la réponse pourrait être quelque chose comme:

public static Bar response(Foo f) { return FooToBar.get(f); } 
public static Foo response(Bar b) { return BarToFoo.get(b); } 
+1

Ou un 'EnumMap' si vous préférez cela à un' switch'. –

+0

(Attention comment vous l'intialiser si - voir Java efficace.) –

1

Design intéressant. Je vois votre besoin, mais qu'allez-vous faire lorsque les exigences changent légèrement, de sorte qu'en réponse à Foo.Epsilon, app_1 devrait envoyer soit soit un Bar.Gamma ou un Bar.Whatsit?

La solution que vous avez envisagée et jetée comme hackish (mettre la relation dans une carte) semble vous donner beaucoup plus de flexibilité, et évite votre référence circulaire. Il garde également la responsabilité partagée: les types de messages eux-mêmes ne devraient pas être tenus de connaître leur réponse, devraient-ils?

0

Vous pouvez utiliser EnumMap et le remplir dans l'une des énumérations.

private static EnumMap<Foo, LinkedList<Bar>> enumAMap; 

public static void main(String[] args) throws Exception { 
    enumAMap = new EnumMap<Foo, LinkedList<Bar>>(Foo.class); 
    System.out.println(Bar.values().length); // initialize enums, prevents NPE 
    for (Foo a : Foo.values()) { 
     for (Bar b : enumAMap.get(a)) { 
      System.out.println(a + " -> " + b); 
     } 
    } 
} 

public enum Foo { 
    Foo1(1), 
    Foo2(2); 

    private int num; 

    private Foo(int num) { 
     this.num = num; 
    } 

    public int getNum() { 
     return num; 
    } 
} 

public enum Bar { 
    Bar1(1, Foo.Foo1), 
    Bar2(2, Foo.Foo1), 
    Bar3(3, Foo.Foo2), 
    Bar4(4, Foo.Foo2); 

    private int num; 
    private Foo foo; 

    private Bar(int num, Foo foo) { 
     this.num = num; 
     this.foo = foo; 
     if (!enumAMap.containsKey(foo)) { 
      enumAMap.put(foo, new LinkedList<Bar>()); 
     } 
     enumAMap.get(foo).addLast(this); 
    } 

    public int getNum() { 
     return num; 
    } 

    public Foo getFoo() { 
     return foo; 
    } 
} 

Sortie:

4 
Foo1 -> Bar1 
Foo1 -> Bar2 
Foo2 -> Bar3 
Foo2 -> Bar4 
Questions connexes