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

时间:2022-01-20 15:42:07

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为null,因此Bar.Alpha的构造函数被传递为null。如果我反转两个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.

有没有办法解决这个问题?我知道我可以在第3课中创建一个静态地图和一个静态地图,但这对我来说是相当讨厌的。我也可以制作引用外部地图的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代表2个应用程序相互发送的消息类型; Foo.b和Bar.f字段代表给定消息的预期响应类型 - 所以在我的示例代码,当app_1收到Foo.A时,需要回复Bar.Alpha,反之亦然。)

Thanks in advance!

提前致谢!

5 个解决方案

#1


21  

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:

#2


10  

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的Bar(并且类似于Bar构造函数中的Bar的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是两种类型的消息。是否可以将它们组合成一种类型?从我所看到的,他们在这里的行为是一样的。这不能解决循环逻辑问题,但它可能会让您对设计有所了解......

#3


3  

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 think performance 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的性能可能更快,但有效Java中描述的那种优雅结构似乎没有得到这个特定问题的支持 - 但是,上面提供的交换解决方案是构建两个静态EnumMaps的好方法,然后响应可能是这样的:

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

#4


1  

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 either a 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?

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

#5


0  

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

#1


21  

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:

#2


10  

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的Bar(并且类似于Bar构造函数中的Bar的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是两种类型的消息。是否可以将它们组合成一种类型?从我所看到的,他们在这里的行为是一样的。这不能解决循环逻辑问题,但它可能会让您对设计有所了解......

#3


3  

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 think performance 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的性能可能更快,但有效Java中描述的那种优雅结构似乎没有得到这个特定问题的支持 - 但是,上面提供的交换解决方案是构建两个静态EnumMaps的好方法,然后响应可能是这样的:

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

#4


1  

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 either a 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?

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

#5


0  

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