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

2022-09-01 09:51:46

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

我有两组枚举,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);
    }
  }
}

上面的代码生成为输出:

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

我理解为什么会发生这种情况 - JVM开始类加载Foo;它在 Foo.A 的构造函数中看到 Bar.Alpha,因此它开始类加载 Bar。它在对Bar.Alpha的构造函数的调用中看到Foo.A引用,但是(因为我们仍然在Foo.A的构造函数中)Foo.A在这一点上是空的,所以Bar.Alpha的构造函数被传递为空。如果我反转两个 for 循环(或以其他方式在 Foo 之前引用 Bar),输出会发生变化,以便 Bar 的值都是正确的,但 Foo 的值不是。

有没有办法解决这个问题?我知道我可以在3rd类中创建静态地图和静态地图,但这对我来说感觉相当麻烦。我还可以制作引用外部映射的Foo.getBar()和Bar.getFoo()方法,这样它甚至不会改变我的界面(我的实际类使用检查器而不是公共字段),但它仍然感觉有点不干净。

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

提前致谢!


答案 1

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

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

另请参阅:


答案 2

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

使用和方法如何?您不是在 Foo 构造函数中设置 Foo's Bar(类似地在 Bar 构造函数中设置 Bar's Foo),而是使用方法进行初始化?例如:Foo.setResponse(Bar b)Bar.setResponse(Foo f)

傅:

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和Bar是两种类型的消息。是否可以将它们组合成一种类型?据我所知,他们在这里的行为是一样的。这并不能解决循环逻辑,但它可能会让你对你的设计有一些其他的了解......