【问题标题】:Java Enums: Two enum types, each containing references to each other?Java Enums:两种枚举类型,每个都包含对彼此的引用?
【发布时间】:2009-10-01 20:53:47
【问题描述】:

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

我有两组枚举,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,反之亦然。)

提前致谢!

【问题讨论】:

    标签: java oop enums classloader cyclic-reference


    【解决方案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
    

    另见:

    【讨论】:

    • 在我看来过于复杂了。 @weiji 的答案更简洁,IMO。为什么这种方法更好(你说“最好的”)?
    • @NoamNelke 我已经对weiji 的方法投了赞成票,这很有趣。尽管我个人认为我推荐的方式要好得多,因为循环引用在其枚举内,但我们的答案之间的一个很大区别是,一旦需要,我的答案还可以让您在返回之前执行任何运行时逻辑,例如: public Bar getBar(boolean nullIfAlpha) { return nullIfAlpha ? null : Bar.Alpha; }。无论如何,我已经将我的答案编辑为“最好的之一”,因为它可能是基于意见的。感谢您的回复!
    【解决方案2】:

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

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

    【讨论】:

    • 嘿,我已经edited your answer 来修复和改进它。请重新编辑,如果您不同意,请回滚。
    【解决方案3】:

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

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

    【讨论】:

    • 或者EnumMap,如果你更喜欢switch
    • (小心你是如何初始化它的 - 请参阅 Effective Java。)
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多