2009-10-01 222 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