2009-10-01 17 views
29

¿Hay alguna forma de evitar los problemas de carga de clases causados ​​por tener dos enumeraciones que se referencian entre sí?Java Enums: dos tipos de enumeración, cada uno con referencias el uno al otro?

que tienen dos juegos de enumeraciones, Foo y Bar, que se define como tal:

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

El código anterior produce como salida:

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

entiendo por qué sucede - la JVM se inicia la carga de clase Foo; ve el Bar.Alpha en el constructor de Foo.A, por lo que comienza la carga de clases en Bar. Ve la referencia de Foo.A en la llamada al constructor de Bar.Alpha, pero (dado que todavía estamos en el constructor de Foo.A) Foo.A es nulo en este punto, por lo que el constructor de Bar.Alpha pasa un nulo. Si invierto los dos bucles de for (o de otro modo referencia a Bar antes de Foo), la salida cambia para que los valores de Bar sean todos correctos, pero los valores de Foo no lo son.

¿Hay alguna forma de evitar esto? Sé que puedo crear un Mapa estático y un Mapa estático en una 3ra clase, pero eso me parece bastante hackoso. También podría hacer los métodos Foo.getBar() y Bar.getFoo() que hacen referencia al mapa externo, por lo que ni siquiera cambiaría mi interfaz (las clases reales que tengo usan inspectores en lugar de campos públicos), pero todavía se siente tipo de sucio para mí.

(La razón por la que hago esto en mi sistema real: Foo y Bar representan tipos de mensajes que se envían 2 aplicaciones; los campos Foo.b y Bar.f representan el tipo de respuesta esperado para un mensaje determinado - entonces en mi código de muestra, cuando app_1 recibe un Foo.A, necesita responder con una barra.Alpha y viceversa.)

¡Gracias de antemano!

Respuesta

21

Una de las mejores maneras estaría utilizando la técnica de polimorfismo enumeración:

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

El código anterior produce la salida que desea:

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

Ver también:

+1

Me parece una complicación excesiva. La respuesta de @weiji es mucho más limpia, IMO. ¿Por qué es mejor este enfoque (dijiste "el mejor")? –

+3

@NoamNelke Ya he votado el enfoque de weiji, que es muy interesante. A pesar de que personalmente creo que es mucho mejor de la manera que lo he recomendado porque la referencia cíclica está dentro de su enumeración, una gran diferencia entre nuestras respuestas es que la mía también le permitirá hacer cualquier lógica de tiempo de ejecución antes de regresar, una vez que sea necesaria, por ejemplo: 'public Bar getBar (boolean nullIfAlpha) {return nullIfAlpha? null: Bar.Alpha; } ' De todos modos, he editado mi respuesta a "uno de los mejores", ya que puede estar basada en opiniones. ¡Gracias por su respuesta! – falsarella

10

El problema no es tanto "dos enumeraciones se refieren entre sí", es más "dos enumeraciones se refieren entre sí en sus constructores". Esta referencia circular es la parte difícil.

¿Qué le parece usar los métodos Foo.setResponse(Bar b) y Bar.setResponse(Foo f)? En lugar de establecer un Foo's Bar en el constructor Foo (y de manera similar un Bar's Foo en el constructor Bar), ¿realiza la inicialización usando un método? Ej .:

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

Además, mencionan que Foo y Bar son dos tipos de mensajes. ¿Sería posible combinarlos en un solo tipo? Por lo que puedo ver, su comportamiento aquí es el mismo. Esto no soluciona la lógica circular, pero podría darle alguna otra idea de su diseño ...

+0

Hey, he [Editado su respuesta] (http://stackoverflow.com/posts/1506635/revisions) para fijar y mejorarlo. Por favor, edítalo de nuevo, o revierte si no estás de acuerdo. – falsarella

3

Puesto que parece que va a ser difícil de codificación de todos modos, ¿por qué no tener algo como

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

para cada enumeración? Parece que tienes algunas respuestas superpuestas en tu ejemplo, por lo que incluso puedes aprovechar el fracaso de los casos.

EDIT:

me gusta la sugerencia de la EnumMap de Tom; I piensa que el rendimiento es probablemente más rápido en EnumMap, pero el tipo de construcción elegante descrita en Java efectivo no parece estar permitido por este problema en particular; sin embargo, la solución de cambio ofrecida anteriormente sería una buena forma de construir dos EnumMaps, la respuesta podría ser algo así como:

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

O un 'EnumMap' si lo prefiere a un' interruptor'. –

+0

(Sin embargo, ten cuidado con cómo lo entiendes - ver Java efectivo.) –

1

Interesante diseño. Veo tu necesidad, pero ¿qué vas a hacer cuando los requisitos cambien ligeramente, para que en respuesta a Foo.Epsilon, la aplicación_1 envíe ya sea a Bar.Gamma o a Bar.Whatsit?

La solución que consideró y desechó como hackish (poner la relación en un mapa) parece darle mucha más flexibilidad y evita su referencia circular. También mantiene la responsabilidad dividida: los tipos de mensajes en sí mismos no deberían ser responsables de conocer su respuesta, ¿o sí?

0

Puede usar EnumMap y llenarlo dentro de una de las enumeraciones.

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

Salida:

4 
Foo1 -> Bar1 
Foo1 -> Bar2 
Foo2 -> Bar3 
Foo2 -> Bar4 
Cuestiones relacionadas