Java 枚举:两种枚举类型,每个类型都包含对彼此的引用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1506410/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 16:48:57  来源:igfitidea点击:

Java Enums: Two enum types, each containing references to each other?

javaoopenumsclassloadercyclic-reference

提问by Sbodd

Is there a way to get around the class-loading issues caused by having two enums that reference each other?

有没有办法解决由两个相互引用的枚举引起的类加载问题?

I have two sets of enumerations, Foo and Bar, defined like so:

我有两组枚举,Foo 和 Bar,定义如下:

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

The above code produces as output:

上面的代码作为输出产生:

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

I understand why it happens - the JVM starts classloading Foo; it sees the Bar.Alpha in Foo.A's constructor, so it starts classloading Bar. It sees the Foo.A reference in the call to Bar.Alpha's constructor, but (since we're still in Foo.A's constructor) Foo.A is null at this point, so Bar.Alpha's constructor gets passed a null. If I reverse the two for loops (or otherwise reference Bar before Foo), the output changes so that Bar's values are all correct, but Foo's values are not.

我明白为什么会发生 - JVM 开始类加载 Foo;它在 Foo.A 的构造函数中看到 Bar.Alpha,所以它开始类加载 Bar。它在对 Bar.Alpha 的构造函数的调用中看到 Foo.A 引用,但是(因为我们仍在 Foo.A 的构造函数中)此时 Foo.A 为空,因此 Bar.Alpha 的构造函数被传递一个空值。如果我反转两个 for 循环(或在 Foo 之前以其他方式引用 Bar),则输出会更改,以便 Bar 的值全部正确,但 Foo 的值不正确。

Is there any way to get around this? I know I can create a static Map and a static Map in a 3rd class, but that feels fairly hackish to me. I could also make Foo.getBar() and Bar.getFoo() methods that refer to the external map, so it wouldn't even change my interface (the actual classes I have use inspectors instead of public fields), but it still feels kind of unclean to me.

有什么办法可以解决这个问题吗?我知道我可以在第三堂课中创建一个静态地图和一个静态地图,但这对我来说感觉相当黑客。我还可以创建引用外部映射的 Foo.getBar() 和 Bar.getFoo() 方法,因此它甚至不会改变我的界面(我使用的实际类使用检查器而不是公共字段),但它仍然感觉对我来说有点不干净。

(The reason I'm doing this in my actual system: Foo and Bar represent types of messages that 2 apps send to each other; the Foo.b and Bar.f fields represent the expected response type for a given message - so in my sample code, when app_1 receives a Foo.A, it needs to reply with a Bar.Alpha and vice-versa.)

(我在实际系统中这样做的原因:Foo 和 Bar 表示两个应用程序相互发送的消息类型;Foo.b 和 Bar.f 字段表示给定消息的预期响应类型 - 所以在我的示例代码,当 app_1 收到一个 Foo.A 时,它需要回复一个 Bar.Alpha,反之亦然。)

Thanks in advance!

提前致谢!

采纳答案by falsarella

One of the best ways would be using the enum polymorphism technique:

最好的方法之一是使用枚举多态技术

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

The above code produces the output you want:

上面的代码产生你想要的输出:

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

See also:

也可以看看:

回答by weiji

The issue isn't so much "two enums reference each other", it's more "two enums reference each other in their constructors". This circular reference is the tricky part.

问题不在于“两个枚举相互引用”,而是“两个枚举在其构造函数中相互引用”。这个循环引用是棘手的部分。

How about using Foo.setResponse(Bar b)and Bar.setResponse(Foo f)methods? Instead of setting a Foo's Bar in the Foo constructor (and similarly a Bar's Foo in the Bar constructor), you do the initialization using a method? E.g.:

使用方法Foo.setResponse(Bar b)Bar.setResponse(Foo f)方法如何?您不是在 Foo 构造函数中设置 Foo's Bar(类似地在 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);
  }
}

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

Also, you mention that Foo and Bar are two types of messages. Would it be possible to combine them into a single type? From what I can see, their behavior here is the same. This doesn't fix the circular logic, but it might give you some other insight into your design...

此外,您提到 Foo 和 Bar 是两种类型的消息。是否可以将它们组合成一种类型?据我所知,他们在这里的行为是一样的。这并不能解决循环逻辑,但它可能会让你对你的设计有一些其他的了解......

回答by Carl

Since it seems you're going to be hard-coding anyways, why not have something like

既然您似乎无论如何都要进行硬编码,为什么不使用类似的东西

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

for each enum? It looks like you have some overlapping responses in your example, so you could even take advantage of cases falling through.

对于每个枚举?看起来您的示例中有一些重叠的响应,因此您甚至可以利用失败的案例。

EDIT:

编辑:

I like Tom's suggestion of the EnumMap; I thinkperformance is probably faster on the EnumMap, but the sort of elegant construction described in Effective Java doesn't seem to be afforded by this particular problem - however, the switch solution offered above would be a good way to construct two static EnumMaps, then the response could be something like:

我喜欢 Tom 对 EnumMap 的建议;我认为EnumMap 的性能可能更快,但是这个特定问题似乎无法提供 Effective Java 中描述的那种优雅的构造——但是,上面提供的 switch 解决方案将是构造两个静态 EnumMap 的好方法,那么响应可能是这样的:

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

回答by CPerkins

Interesting design. I see your need, but what are you going to do when the requirements shift slightly, so that in response to Foo.Epsilon, app_1 should send eithera Bar.Gamma or a Bar.Whatsit?

有趣的设计。我看你的需要,但什么是你打算怎么办时要求稍微移动,从而使响应Foo.Epsilon,APP_1应发出一Bar.Gamma或Bar.Whatsit?

The solution you considered and discarded as hackish (putting the relation into a map) seems to give you much more flexibility, and avoids your circular reference. It also keeps the responsibility partitioned: the message types themselves shouldn't be responsible for knowing their response, should they?

您考虑并丢弃的解决方案(将关系放入地图)似乎为您提供了更大的灵活性,并避免了您的循环引用。它还保持了责任划分:消息类型本身不应该负责知道他们的响应,对吗?

回答by MForm

You can use EnumMap, and fill it within one of the enums.

您可以使用 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;
    }
}

Output:

输出:

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