Java枚举:两个枚举类型,每个枚举类型包含彼此的引用?
有没有办法避免由两个相互引用的枚举引起的类加载问题?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进行回复,反之亦然。)
在此先感谢!
一个最好的方法将使用枚举多态性技术:
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
参见:
问题不在于“两枚枚举相互引用”,它更多的是“两枚枚举在其构造函数中相互引用”。这个循环引用是棘手的部分。
如何使用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和酒吧有两种类型的消息。将它们组合成单一类型是否可能?从我所看到的,他们在这里的行为是一样的。这不固定循环逻辑,但它可能给你一些其他的洞察您的设计......
嘿,我[编辑你的答案(http://stackoverflow.com/posts/1506635/revisions)来解决和改进。请重新编辑它,或者如果您不同意,请回滚。 – falsarella 2015-05-19 17:48:26
因为它似乎你将是反正硬编码,为什么不能有像
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); }
或者'EnumMap',如果你更喜欢''switch'。 – 2009-10-01 22:02:57
(小心你是如何初始化的 - 参见Effective Java。) – 2009-10-01 22:03:30
有趣的设计。我看到了你的需求,但是当需求发生轻微变化时你会怎么做,所以为了响应Foo.Epsilon,app_1应该发送或者 Bar.Gamma或Bar.Whatsit?
您考虑并抛弃为hackish(将关系放入地图)的解决方案似乎给了您更多的灵活性,并避免了您的循环引用。它也将责任分开:消息类型本身不应该负责知道他们的回应,如果他们?
您可以使用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
对我来说似乎过于复杂。 @维基的回答更清洁,海事组织。为什么这种方法更好(你说“最好”)? – 2015-05-19 15:26:23
@NoamNelke我已经投了韦基的方法,这很有趣。尽管我个人认为它的建议方式要好得多,因为循环引用在它的枚举范围内,我们的答案之间的一个很大的区别是,在返回之前,我可以在返回之前执行任何运行时逻辑,例如: '公共酒吧getBar(布尔nullIfAlpha){返回nullIfAlpha? null:Bar.Alpha; }'。无论如何,我已经将我的答案编辑为“最好的”之一,因为它可能是基于意见的。感谢您的回复! – falsarella 2015-05-19 17:29:38