32

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

我有两组枚举,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 的值不是。

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

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

提前致谢!

4

6 回答 6

25

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

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

也可以看看:

于 2011-12-23T12:53:45.183 回答
11

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

如何使用Foo.setResponse(Bar b)Bar.setResponse(Foo f)方法?您不是在 Foo 构造函数中设置 Foo 的 Bar(类似地在 Bar 构造函数中设置 Bar 的 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 和 Bar 是两种类型的消息。是否有可能将它们组合成一个类型?据我所知,他们在这里的行为是一样的。这并不能解决循环逻辑,但它可能会让您对您的设计有一些其他的见解......

于 2009-10-01T21:35:00.703 回答
4

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

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

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

编辑:

我喜欢 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); }
于 2009-10-01T21:50:39.577 回答
2

有趣的设计。我看到了您的需求,但是当需求略有变化时您会怎么做,以便响应 Foo.Epsilon,app_1 应该发送Bar.Gamma或 Bar.Whatsit?

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

于 2009-10-02T11:32:40.837 回答
1

您可以使用 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
于 2017-07-06T15:20:46.363 回答
0

这是@falsarella 答案的更新版本。这使用 Java 8 Lambdas 和 java.util.function,但是主体是相同的。使用较新的语法会更简洁一点:

import java.util.function.Supplier;

public class EnumTest {

    public enum Foo {
        A(() -> Bar.Alpha), B(() -> Bar.Beta), C(() -> Bar.Delta);

        private final Supplier<Bar> bar;

        private Foo(Supplier<Bar> bar) {
            this.bar = bar;
        }

        public Bar getBar() {
            return bar.get();
        }
    }

    public enum Bar {
        Alpha(() -> Foo.A), Beta(() -> Foo.B), Delta(() -> Foo.C);

        private final Supplier<Foo> foo;

        private Bar(Supplier<Foo> foo) {
            this.foo = foo;
        }

        public Foo getFoo() {
            return foo.get();
        }
    }

    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());
        }
    }
}
于 2021-05-17T13:12:37.250 回答