2009-10-01 223 views
29

有沒有辦法避免由兩個相互引用的枚舉引起的類加載問題?Java枚舉:兩個枚舉類型,每個枚舉類型包含彼此的引用?

我有兩套枚舉,Foo和酒吧的,像這樣定義的:

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.A的構造函數中的Bar.Alpha,因此它開始加載Bar。它在調用Bar.Alpha的構造函數時看到Foo.A引用,但是(因爲我們仍然在Foo.A的構造函數中)Foo.A此時爲null,因此Bar.Alpha的構造函數會傳遞一個null。如果我將兩個for循環倒轉(或者在Foo之前​​引用Bar),則輸出會發生變化,以便Bar的值全部正確,但Foo的值不正確。

有什麼辦法可以解決這個問題嗎?我知道我可以在第三堂課創建一個靜態地圖和一個靜態地圖,但是這對我來說感覺相當不好。我也可以創建引用外部映射的Foo.getBar()和Bar.getFoo()方法,所以它甚至不會改變我的界面(實際的類我使用檢查器而不是公共字段),但它仍然感覺對我不潔。我在我的實際系統中這樣做的原因:Foo和Bar表示兩個應用程序相互發送的消息類型; Foo.b和Bar.f字段表示給定消息的預期響應類型 - 所以在我的示例代碼中,當app_1收到Foo.A時,它需要使用Bar.Alpha進行回覆,反之亦然。)

在此先感謝!

回答

21

一個最好的方法將使用枚舉多態性技術

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 

參見:

+1

對我來說似乎過於複雜。 @維基的回答更清潔,海事組織。爲什麼這種方法更好(你說「最好」)? – 2015-05-19 15:26:23

+3

@NoamNelke我已經投了韋基的方法,這很有趣。儘管我個人認爲它的建議方式要好得多,因爲循環引用在它的枚舉範圍內,我們的答案之間的一個很大的區別是,在返回之前,我可以在返回之前執行任何運行時邏輯,例如: '公共酒吧getBar(布爾nullIfAlpha){返回nullIfAlpha? null:Bar.Alpha; }'。無論如何,我已經將我的答案編輯爲「最好的」之一,因爲它可能是基於意見的。感謝您的回覆! – falsarella 2015-05-19 17:29:38

10

問題不在於「兩枚枚舉相互引用」,它更多的是「兩枚枚舉在其構造函數中相互引用」。這個循環引用是棘手的部分。

如何使用Foo.setResponse(Bar b)Bar.setResponse(Foo f)方法?而不是在Foo構造函數中設置Foo's Bar(並且類似Bar構造函數中的Bar's 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和酒吧有兩種類型的消息。將它們組合成單一類型是否可能?從我所看到的,他們在這裏的行爲是一樣的。這不固定循環邏輯,但它可能給你一些其他的洞察您的設計......

+0

嘿,我[編輯你的答案(http://stackoverflow.com/posts/1506635/revisions)來解決和改進。請重新編輯它,或者如果您不同意,請回滾。 – falsarella 2015-05-19 17:48:26

3

因爲它似乎你將是反正硬編碼,爲什麼不能有像

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

爲每個枚舉?在你的例子中,你看起來有一些重疊的反應,所以你甚至可以利用案例中的情況。編輯:

我喜歡湯姆的EnumMap的建議;我認爲認爲 EnumMap的性能可能更快,但有效Java中描述的那種優雅構造看起來沒有得到這個特定問題的支持 - 然而,上面提供的開關解決方案將是構建兩個靜態EnumMaps,那麼響應可能類似於:

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

或者'EnumMap',如果你更喜歡''switch'。 – 2009-10-01 22:02:57

+0

(小心你是如何初始化的 - 參見Effective Java。) – 2009-10-01 22:03:30

1

有趣的設計。我看到了你的需求,但是當需求發生輕微變化時你會怎麼做,所以爲了響應Foo.Epsilon,app_1應該發送或者 Bar.Gamma或Bar.Whatsit?

您考慮並拋棄爲hackish(將關係放入地圖)的解決方案似乎給了您更多的靈活性,並避免了您的循環引用。它也將責任分開:消息類型本身不應該負責知道他們的迴應,如果他們?

0

您可以使用EnumMap,並將其填入其中一個枚舉中。

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