【问题标题】:Illegal Forward Reference and Enums非法前向引用和枚举
【发布时间】:2011-08-06 09:43:04
【问题描述】:

我正在用 Java 编写一个由瓷砖网格组成的游戏。我不能直观地定义瓷砖的边缘以及它们如何相互关联,例如要获得瓷砖的另一边,我希望能够输入TOP.opposite()。然而,当使用枚举来定义这些边时,我最终不得不在构造函数中转发引用其中至少两个:

public enum Edge {

   TOP(Edge.BOTTOM), //illegal forward reference
   BOTTOM(Edge.TOP),
   LEFT(Edge.RIGHT), //illegal forward reference
   RIGHT(Edge.LEFT);

   private Edge opposite;

   private Edge(Edge opp){
      this.opposite = opp;
   }

   public Edge opposite(){
      return this.opposite;
   }
}

有没有什么方法可以使用同样简单的枚举来解决这个问题?

【问题讨论】:

    标签: java enums


    【解决方案1】:

    添加方法opposite返回枚举对象

    你可以定义一个方法,opposite()

    在现代 Java 中,switch 表达式

    在现代 Java 中,我们可以使用 switch expression。编译器确保我们已经涵盖了所有可能的情况。

    enum Edge
    {
        TOP, BOTTOM, LEFT, RIGHT;
    
        public Edge opposite ( )
        {
            return switch ( this )
                    {
                        case TOP -> BOTTOM;
                        case BOTTOM -> TOP;
                        case LEFT -> RIGHT;
                        case RIGHT -> LEFT;
                    };
        }
    }
    

    用法:

    System.out.println( Edge.TOP.opposite() );
    

    底部

    在早期的 Java 中,switch

    在较旧的 Java 中,使用以下代码中的语法。

    请注意default 案例的必要性,以防您向枚举添加元素或无意中从switch 中删除了案例。

    public enum Edge {
        TOP,
        BOTTOM,
        LEFT,
        RIGHT;
    
        public Edge opposite() {
            switch (this) {
                case TOP:
                    return BOTTOM;
                case BOTTOM:
                    return TOP;
                case LEFT:
                    return RIGHT;
                case RIGHT:
                    return LEFT;
                default:
                    throw new RuntimeException("Oh dear");
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我的方法是使用序数。这是一个简单的示例,但对于更复杂的示例,请参见下文。

      public enum Edge {
          // Don't change the order! This class uses ordinal() in an arithmetic context.
          TOP,    // = 0
          LEFT,   // = 1
          RIGHT,  // = 2
          BOTTOM; // = 3
      
          public Edge other() {
              return values()[3 - ordinal()];
          }
      }
      

      虽然不鼓励使用 ordinal 易碎,但在定义的同一枚举中使用 ordinal 不那么易碎,并且在此处通过注释进一步减轻了它的影响。虽然上面的例子很简单,但下一个例子就不那么简单了。比较原始方式和使用序数的方式:

      从 98 行开始:

      public enum Axes {
          NONE,
          HORIZONTAL,
          VERTICAL,
          BOTH;
      
          public Axes add(Axes axes) {
              switch (axes) {
                  case HORIZONTAL:
                      if (this == NONE)
                          return HORIZONTAL;
                      if (this == VERTICAL)
                          return BOTH;
                      break;
                  case VERTICAL:
                      if (this == NONE)
                          return VERTICAL;
                      if (this == HORIZONTAL)
                          return BOTH;
                      break;
                  case BOTH:
                      return BOTH;
                  default:
                      throw new AssertionError(axes);
              }
              return this;
          }
      
          public Axes remove(Axes axes) {
              switch (axes) {
                  case HORIZONTAL:
                      if (this == HORIZONTAL)
                          return NONE;
                      if (this == BOTH)
                          return VERTICAL;
                      break;
                  case VERTICAL:
                      if (this == VERTICAL)
                          return NONE;
                      if (this == BOTH)
                          return HORIZONTAL;
                      break;
                  case BOTH:
                      return NONE;
                  default:
                      throw new AssertionError(axes);
              }
              return this;
          }
      
          public Axes toggle(Axes axes) {
              switch (axes) {
                  case NONE:
                      return this;
                  case HORIZONTAL:
                      switch (this) {
                          case NONE:
                              return HORIZONTAL;
                          case HORIZONTAL:
                              return NONE;
                          case VERTICAL:
                              return BOTH;
                          case BOTH:
                              return VERTICAL;
                          default:
                              throw new AssertionError(axes);
                      }
                  case VERTICAL:
                      switch (this) {
                          case NONE:
                              return VERTICAL;
                          case HORIZONTAL:
                              return BOTH;
                          case VERTICAL:
                              return NONE;
                          case BOTH:
                              return HORIZONTAL;
                          default:
                              throw new AssertionError(axes);
                      }
                  case BOTH:
                      switch (this) {
                          case NONE:
                              return BOTH;
                          case HORIZONTAL:
                              return VERTICAL;
                          case VERTICAL:
                              return HORIZONTAL;
                          case BOTH:
                              return NONE;
                          default:
                              throw new AssertionError(axes);
                      }
                  default:
                      throw new AssertionError(axes);
              }
          }
      }
      

      到 19 行:

      public enum Axes {
          // Don't change the order! This class uses ordinal() as a 2-bit bitmask.
          NONE,       // = 0 = 0b00
          HORIZONTAL, // = 1 = 0b01
          VERTICAL,   // = 2 = 0b10
          BOTH;       // = 3 = 0b11
      
          public Axes add(Axes axes) {
              return values()[ordinal() | axes.ordinal()];
          }
      
          public Axes remove(Axes axes) {
              return values()[ordinal() & ~axes.ordinal()];
          }
      
          public Axes toggle(Axes axes) {
              return values()[ordinal() ^ axes.ordinal()];
          }
      }
      

      【讨论】:

        【解决方案3】:

        您可以做到这一点,但并不那么直观。

        public enum Edge {
            TOP, BOTTOM, LEFT, RIGHT;
            private Edge opposite;
        
            static {
                TOP.opposite = BOTTOM;
                BOTTOM.opposite = TOP;
                LEFT.opposite = RIGHT;
                RIGHT.opposite = LEFT;
            }
            public Edge opposite(){
                return this.opposite;
            }
        }
        

        【讨论】:

        • 不幸的是,如果在另一个类的静态初始化器中需要枚举,那么你会得到异常class .....EnumName not an enum。我将不得不使用地图...
        • @P.Péter 在另一个类、静态初始化程序或其他任何地方使用枚举应该没有问题。
        • @PeterLawrey 你是对的,我犯了另一个错误,导致了上述异常。您的解决方案效果很好!
        • 属性最终和公开时不同。
        • @MattLBeck 是否保证枚举实例上的引用在客户端收到静态块时已经运行?我怀疑您正在泄漏未完全初始化的枚举实例。 TOP.opposite = BOTTOM 行之后,如果客户调用 TOP.opposite.opposite,在某些情况下,您会得到未定义的行为。
        【解决方案4】:

        使用 Java 8 lambda:

        public enum Edge {
          TOP(() -> Edge.BOTTOM),
          BOTTOM(() -> Edge.TOP),
          LEFT(() -> Edge.RIGHT),
          RIGHT(() -> Edge.LEFT);
        
          private Supplier<Edge> opposite;
        
          private Edge(Supplier<Edge> opposite) {
            this.opposite = opposite;
          }
        
          public Edge opposite() {
            return opposite.get();
          }
        }
        

        【讨论】:

        • 虽然理论上这是有道理的,而且似乎是解决问题的一种花边方法,但这仍然会导致编译器错误“非法前向引用”
        • 我必须同意@Spenhouet,我在这里是因为使用 lambdas 后出现的错误
        • 但是我已经通过使用这个答案中的解决方案解决了它:stackoverflow.com/questions/23608885/…
        【解决方案5】:

        我更喜欢这个:

        public enum Edge {
           TOP,
           BOTTOM,
           LEFT,
           RIGHT;
        
           private Link link;
        
           private Link getLink() {
             if (link == null) {
                link = Link.valueOf(name());
             }
             return link;
           }
        
           public Edge opposite() {
              return getLink().opposite();
           }
        }
        
        public enum Link {
           TOP(Edge.BOTTOM),
           BOTTOM(Edge.TOP),
           LEFT(Edge.RIGHT),
           RIGHT(Edge.LEFT);
        
           private Edge opposite;
        
           private Link(Edge opp) {
              this.opposite = opp;
           }
        
           public Edge opposite() {
              return this.opposite;
           }
        }
        

        【讨论】:

          【解决方案6】:
          public enum Edge {
          
              TOP,
              BOTTOM(Edge.TOP),
              LEFT,
              RIGHT(Edge.LEFT);
          
              private Edge opposite;
          
              private Edge() {
          
              }
              private Edge(Edge opp) {
                  this.opposite = opp;
                  opp.opposite = this;
              }
          
              public Edge opposite() {
                  return this.opposite;
              }
          }
          

          【讨论】:

          • 它非常简洁!我们甚至可以先写 TOP, LEFT
          【解决方案7】:
          enum Edge {
              TOP {
                  @Override
                  public Edge opposite() {
                      return BOTTOM;
                  }
              },
              BOTTOM {
                  @Override
                  public Edge opposite() {
                      return TOP;
                  }
              },
              LEFT {
                  @Override
                  public Edge opposite() {
                      return RIGHT;
                  }
              },
              RIGHT {
                  @Override
                  public Edge opposite() {
                      return LEFT;
                  }
              };
          
              public abstract Edge opposite();
          }
          

          【讨论】:

            【解决方案8】:

            您还可以在枚举中使用 静态内部类

            public enum EnumTest     
            {     
            NORTH( Orientation.VERTICAL ),     
            SOUTH( Orientation.VERTICAL ),     
            EAST( Orientation.HORIZONTAL ),     
            WEST( Orientation.HORIZONTAL );     
            
            private static class Orientation  
            {  
            private static final String VERTICAL = null;     
            private static final String HORIZONTAL = null;     
            }
            }
            

            here 窃取:)

            【讨论】:

              【解决方案9】:

              您可以创建一个静态Map,其中键是原始枚举,值是相反的边缘。在静态块中初始化它并从opposite() 方法返回映射。

              private static Map<Edge, Edge> oppostiteMapping;
              
              static {
                oppositeMapping = new EnumMap<Edge, Edge>();
                oppositeMapping.put(TOP, BOTTOM);
                ...
              }
              
              public Edge opposite() {
                  return oppositeMapping.get(this);
              } 
              

              编辑:正如评论中建议的更好地使用 EnumMap,所以我相应地升级了

              顺便说一句。当您创建静态fromString() 方法等时,这种方法通常很有用。

              【讨论】:

                【解决方案10】:

                您可以改用内部地图来定义这些关联。如果在初始化 Map 时,您已经创建了所有枚举值,则此方法有效:

                public enum Edge {
                
                  TOP,
                  BOTTOM,
                  LEFT,
                  RIGHT;
                
                  private static final Map<Edge, Edge> opposites = 
                        new EnumMap<Edge, Edge>(Edge.class);
                  static {
                    opposites.put(TOP, BOTTOM);
                    opposites.put(BOTTOM, TOP);
                    opposites.put(LEFT, RIGHT);
                    opposites.put(RIGHT, LEFT);
                  }
                
                  public Edge opposite(){
                    return opposites.get(this);
                  }
                }
                

                【讨论】:

                  【解决方案11】:

                  这是另一种方式

                  public enum Edge {
                  
                      TOP("BOTTOM"),
                      BOTTOM("TOP"),
                      LEFT("RIGHT"),
                      RIGHT("LEFT");
                  
                      private String opposite;
                  
                      private Edge(String opposite){
                          this.opposite = opposite;
                      }
                  
                      public Edge opposite(){
                          return valueOf(opposite);
                      }
                  
                  }
                  

                  然而,Peter Lawrey 的解决方案更高效且编译时安全。

                  【讨论】:

                    猜你喜欢
                    • 2015-09-18
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-12-11
                    • 2011-10-17
                    • 2013-02-14
                    • 2011-11-20
                    • 2016-12-08
                    相关资源
                    最近更新 更多