Enumeración de Java: ¿dos tipos de enumeración, cada uno con referencias el uno al otro?

¿Hay alguna forma de evitar los problemas de carga de clases causados ​​por tener dos enumeraciones que se referencian entre sí?

Tengo dos conjuntos de enumeraciones, Foo y Bar, definidos así:

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 " + fb); } for (Bar b: Bar.values()) { System.out.println(b + " foo " + bf); } } } 

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 comienza a cargar clases con 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 estoy haciendo esto en mi sistema actual: Foo y Bar representan tipos de mensajes que se envían 2 aplicaciones entre sí; los campos Foo.b y Bar.f representan el tipo de respuesta esperada para un mensaje dado, así que en mi código de ejemplo, cuando app_1 recibe un Foo.A, necesita responder con un Bar.Alpha y viceversa).

¡Gracias por adelantado!

Una de las mejores maneras sería usar la técnica de polymorphism 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()); } } } 

El código anterior produce el resultado que desea:

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

Ver también:

  • Método de redefinición de una enumeración específica

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 Foo.setResponse(Bar b) usar los 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? P.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, menciona 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 proporcionarle otra información sobre su diseño …

Dado que parece que va a ser difícil de codificar de todos modos, ¿por qué no tener algo así como

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

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

EDITAR:

Me gusta la sugerencia de Tom de EnumMap; Creo 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 a cargo de este problema en particular, sin embargo, la solución de cambio ofrecida anteriormente sería una buena forma de construir dos EnumMaps estáticos. entonces 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); } 

Diseño interesante Veo tu necesidad, pero ¿qué vas a hacer cuando los requisitos cambien ligeramente, de modo que en respuesta a Foo.Epsilon, la aplicación_1 debería enviar un Bar.Gamma o un Bar.Whatsit?

La solución que consideró y descartó como hackish (poner la relación en un mapa) parece brindarle 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í?

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

 private static EnumMap> enumAMap; public static void main(String[] args) throws Exception { enumAMap = new EnumMap>(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()); } 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 
Intereting Posts