Java Enum: due tipi di enumerazione, ciascuno contenente riferimenti l’uno all’altro?

C’è un modo per aggirare i problemi di caricamento della class causati dall’avere due enumerazioni che fanno riferimento l’un l’altro?

Ho due serie di enumerazioni, Foo e Bar, definite in questo modo:

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

Il codice sopra riportato produce come output:

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

Capisco perché succede: la JVM inizia a classificare Foo; vede il Bar.Alpha nel costruttore di Foo.A, quindi avvia il caricamento delle classi Bar. Vede il riferimento Foo.A nella chiamata al costruttore di Bar.Alpha, ma (dato che siamo ancora nel costruttore di Foo.A) Foo.A è nullo a questo punto, quindi il costruttore di Bar.Alpha riceve un null. Se inverto i due cicli for (o altrimenti rimandi Bar prima di Foo), l’output cambia in modo che i valori di Bar siano tutti corretti, ma i valori di Foo no.

C’è un modo per aggirarlo? So di poter creare una mappa statica e una mappa statica in una 3a class, ma per me è abbastanza logico. Potrei anche fare i metodi Foo.getBar () e Bar.getFoo () che si riferiscono alla mappa esterna, quindi non cambierebbe nemmeno la mia interfaccia (le classi effettive che uso ispettori invece dei campi pubblici), ma si sente ancora tipo di impuro per me.

(Il motivo per cui sto facendo questo nel mio sistema attuale: Foo e Bar rappresentano tipi di messaggi che 2 app si inviano l’un l’altro, i campi Foo.b e Bar.f rappresentano il tipo di risposta previsto per un dato messaggio – quindi nel mio codice di esempio, quando app_1 riceve un Foo.A, ha bisogno di rispondere con un Bar.Alpha e viceversa.)

Grazie in anticipo!

Uno dei modi migliori sarebbe utilizzare la tecnica del polimorfismo 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()); } } } 

Il codice sopra riportato produce l’output desiderato:

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

Guarda anche:

  • Metodo di overridding di un enum specifico

Il problema non è tanto “due enumerazioni si richiamano”, è più “due enumerazioni si richiamano l’un l’altra nei loro costruttori “. Questo riferimento circolare è la parte difficile.

Che ne dici di usare i Foo.setResponse(Bar b) e Bar.setResponse(Foo f) ? Invece di impostare una Foo’s Bar nel costruttore Foo (e allo stesso modo un Foo della barra nel costruttore di Bar), si fa l’inizializzazione usando un metodo? Per esempio:

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

Inoltre, dici che Foo e Bar sono due tipi di messaggi. Sarebbe ansible combinarli in un unico tipo? Da quello che posso vedere, il loro comportamento qui è lo stesso. Questo non risolve la logica circolare, ma potrebbe darti qualche altra idea sul tuo progetto …

Dal momento che sembra che tu stia programmando comunque, perché non avere qualcosa del genere

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

per ogni enum? Sembra che tu abbia alcune risposte sovrapposte nel tuo esempio, in modo che tu possa persino trarre vantaggio dai casi che cadono.

MODIFICARE:

Mi piace il suggerimento di Tom sull’EnumMap; Penso che le prestazioni siano probabilmente più veloci su EnumMap, ma il tipo di elegante costruzione descritta in Effective Java non sembra essere consentito da questo particolare problema – tuttavia, la soluzione switch offerta sopra sarebbe un buon modo per build due EnumMaps statici, allora la risposta potrebbe essere qualcosa come:

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

Design interessante. Vedo il tuo bisogno, ma che cosa hai intenzione di fare quando i requisiti cambiano leggermente, in modo che in risposta a Foo.Epsilon, app_1 dovrebbe inviare un Bar.Gamma o una Bar.Whatsit?

La soluzione che hai considerato e scartata come hackish (mettendo la relazione in una mappa) sembra darti molta più flessibilità ed evita il tuo riferimento circolare. Mantiene anche la ripartizione delle responsabilità: i tipi di messaggi stessi non dovrebbero essere responsabili di conoscere la loro risposta, dovrebbero?

Puoi usare EnumMap e riempirlo all’interno di una delle enumerazioni.

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

Produzione:

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