Frage

Gibt es eine Möglichkeit, um die Klasse-Probleme beim Laden von zwei Aufzählungen verursacht zu erhalten, die aufeinander verweisen?

Ich habe zwei Sätze von Aufzählungen, Foo und Bar, definiert wie folgt:

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

Der obige Code erzeugt als Ausgabe:

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

Ich verstehe, warum es passiert - die JVM Foo startet Classloading; es sieht die Bar.Alpha in Foo.A Konstruktor, so beginnt es Bar Classloading. Sie sieht die Foo.A Referenz in dem Aufruf von Bar.Alpha Konstruktor, aber (da wir noch in Foo.A Konstruktor sind) Foo.A an dieser Stelle null ist, so Bar.Alpha Konstruktor eine Null übergeben wird. Wenn ich die zwei for-Schleifen (oder anderweitig Referenz Bar vor Foo) umgekehrt, ändert sich der Ausgang, so dass Bar Werte sind alle richtig, aber Foo Werte sind es nicht.

Gibt es eine Möglichkeit, dies zu umgehen? Ich weiß, ich kann eine statische Karte und eine statische Karte in einer dritten Klasse erstellen, aber das fühlt sich für mich ziemlich hackish. Ich könnte auch Foo.getBar () und Bar.getFoo () Methoden machen, die auf die externe Karte beziehen, so würde es nicht einmal meine Schnittstelle ändern (die eigentlichen Klassen I Verwendung Inspektoren statt öffentlichen Bereichen haben), aber es fühlt sich immer noch Art unrein zu mir.

(Der Grund, warum ich in meinem aktuellen System tue dies bin: Foo und Bar repräsentieren Arten von Nachrichten, die zwei Anwendungen untereinander austauschen, die Foo.b und Bar.f Felder repräsentieren den erwarteten Antworttyp für eine bestimmte Nachricht - so in meinem Beispielcode, wenn app_1 eine Foo.A erhält, muss es mit einem Bar.Alpha und umgekehrt antworten.)

Vielen Dank im Voraus!

War es hilfreich?

Lösung

Eine der besten Möglichkeiten, würde die ENUM-Polymorphismus-Technik wird mit :

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

Der obige Code erzeugt die Ausgabe, die Sie wollen:

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

Siehe auch:

Andere Tipps

Das Problem ist nicht so sehr „zwei Aufzählungen sie verweisen“, so dass es „zwei Aufzählungen aufeinander verweisen in ihren Konstrukteuren “ sind. Diese kreisförmige Referenz ist der schwierige Teil.

Wie wäre es Foo.setResponse(Bar b) und Bar.setResponse(Foo f) Methoden verwenden? Anstelle eines Foo Bar in der Foo-Konstruktor (und in ähnlicher Weise eine Bar der Foo in der Bar-Konstruktor) einstellen, haben Sie die Initialisierung mit einer Methode? Z.B.:

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

Auch Sie erwähnen, dass Foo und Bar sind zwei Arten von Nachrichten. Wäre es möglich, sie in eine einzige Art zu kombinieren? Von dem, was ich sehen kann, hier, ihr Verhalten ist das gleiche. Dies schließt nicht die zirkuläre Logik beheben, aber es könnte Sie einen anderen Einblick in Ihr Design geben ...

Da es scheint, du wirst sowieso hart Codierung sein, warum nicht so etwas wie

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

für jede Enum? Es sieht aus wie Sie einige überlappende Antworten in Ihrem Beispiel, so dass Sie könnte sogar den Vorteil der Fälle fallen durch übernehmen.

EDIT:

Ich mag Tom Vorschlag des EnumMap; I denkt Leistung ist wahrscheinlich schneller auf dem EnumMap, aber die Art der eleganten Konstruktion beschrieben in Effective Java scheint nicht von diesem speziellen Problem gewährt werden - jedoch bot die Switch-Lösung oben wäre ein guter sein Art und Weise zwei statische EnumMaps zu konstruieren, dann ist die Antwort könnte etwas wie:

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

Interessantes Design. Ich sehe Ihre Notwendigkeit, aber was werden Sie tun, wenn die Anforderungen leicht verschieben, so dass als Reaktion auf Foo.Epsilon, app_1 senden sollte entweder ein Bar.Gamma oder ein Bar.Whatsit?

Die Lösung, die Sie betrachtet und als hackish verworfen (die Beziehung in eine Karte setzen) scheint Ihnen viel mehr Flexibilität zu geben, und vermeidet Ihr Kreis Referenz. Es hält auch die Verantwortung aufgeteilt: die Nachrichtentypen selbst nicht zu wissen, ihre Reaktion verantwortlich sein sollte, sollten sie auch?

Sie können EnumMap verwenden, und füllen Sie ihn in einem der Aufzählungen.

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

Ausgabe:

4
Foo1 -> Bar1
Foo1 -> Bar2
Foo2 -> Bar3
Foo2 -> Bar4
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top