web-dev-qa-db-ja.com

Java列挙型:それぞれが相互参照を含む2つの列挙型?

相互に参照する2つの列挙型を持つことによって引き起こされるクラスローディングの問題を回避する方法はありますか?

FooとBarの2セットの列挙があり、次のように定義されています。

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

上記のコードは出力として生成されます:

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

なぜそれが起こるのか理解しています-JVMはFooのクラスロードを開始します。 Foo.AのコンストラクターでBar.Alphaを確認するため、Barのクラスロードを開始します。 Bar.Alphaのコンストラクターの呼び出しでFoo.A参照が表示されますが、(まだFoo.Aのコンストラクターにいるため)この時点でFoo.Aはnullであるため、Bar.Alphaのコンストラクターにはnullが渡されます。 2つのforループを逆にすると(またはFooの前にBarを参照すると)、出力が変化してBarの値はすべて正しくなりますが、Fooの値は正しくありません。

これを回避する方法はありますか? 3番目のクラスで静的マップと静的マップを作成できることは知っていますが、それは私にはかなりハックだと感じます。外部マップを参照するFoo.getBar()メソッドとBar.getFoo()メソッドを作成することもできるので、インターフェイス(パブリックフィールドの代わりにインスペクターを使用する実際のクラス)も変更されませんが、それでも感じます私にはちょっと汚れています。

(実際のシステムでこれを行っている理由:FooとBarは、2つのアプリが相互に送信するメッセージのタイプを表します。Foo.bフィールドとBar.fフィールドは、特定のメッセージに対して予想される応答タイプを表します。サンプルコード、app_1がFoo.Aを受信すると、Bar.Alphaで応答する必要があります。その逆も同様です。)

前もって感謝します!

30
Sbodd

最良の方法の1つは、列挙型ポリモーフィズム手法を使用することです。

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

上記のコードは、必要な出力を生成します。

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

参照:

21
falsarella

問題は「2つの列挙型が相互に参照する」ということではなく、「2つの列挙型が相互に参照する」ということですコンストラクター内で "。この循環参照は注意が必要な部分です。

Foo.setResponse(Bar b)メソッドとBar.setResponse(Foo f)メソッドを使用するのはどうですか? FooコンストラクターでFoo'sBarを設定する代わりに(同様にBarコンストラクターでBar's Fooを設定する)、メソッドを使用して初期化を行いますか?例えば。:

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

バー:

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

また、FooとBarは2種類のメッセージであるとおっしゃっています。それらを1つのタイプに組み合わせることが可能でしょうか?私が見ることができることから、ここでの彼らの行動は同じです。これは循環論理を修正しませんが、それはあなたにあなたのデザインへの他の洞察を与えるかもしれません...

11
weiji

とにかくハードコーディングするように思われるので、次のようなものを持ってみませんか

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

列挙型ごとに?あなたの例ではいくつかの重複する応答があるように見えるので、失敗したケースを利用することさえできます。

編集:

トムのEnumMapの提案が好きです。 I think EnumMapのパフォーマンスはおそらく速いですが、Effective Java)で説明されているようなエレガントな構造は、この特定の問題では提供されないようです-ただし、上記のスイッチソリューションは、2つの静的EnumMapを作成するための優れた方法であり、応答は次のようになります。

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

面白いデザイン。私はあなたの必要性を理解していますが、要件がわずかにシフトしたときに何をしますか?Foo.Epsilonに応答して、app_1はどちらか Bar.GammaまたはBar.Whatsitを送信する必要がありますか?

ハック(関係をマップに入れる)と見なして破棄したソリューションは、はるかに柔軟性があり、循環参照を回避しているようです。また、責任を分割します。メッセージタイプ自体が、応答を知る責任を負わないようにする必要がありますか?

2
CPerkins

EnumMapを使用して、列挙型の1つに入力できます。

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

出力:

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