【问题标题】:use of ordinal inside Java Enum definition在 Java 枚举定义中使用序数
【发布时间】:2013-01-24 01:07:28
【问题描述】:

我注意到Java Enum Documentation 声明了序数方法:

返回此枚举常量的序号(它在枚举声明中的位置,其中初始常量的序号为零)。大多数程序员都不会使用这种方法。它设计用于复杂的基于枚举的数据结构,例如 EnumSet 和 EnumMap。

我了解所有在线示例,建议不要使用 ordinal 来索引数组,而是使用 EnumMap。特别是Item 33 of Effective Java 但我的问题是:可以在我的Enum 定义中使用它吗? 例如我的代码如下:

public enum Direction {
   NORTH(0, 1), NORTH_EAST(1, 1), EAST(1, 0), SOUTH_EAST(1, -1),
   SOUTH(0, -1), SOUTH_WEST(-1, 1), WEST(-1, 0), NORTH_WEST(-1, 1);

   private final int xOffset;
   private final int yOffset;

   private final static int DEGREES = 360;

   private Direction(int xOffset, int yOffset) {
     this.xOffset = xOffset;
     this.yOffset = yOffset;
   }

  public Position move(Position position) {
     return new Position(position.getX() + xOffset, position.getY() + yOffset);
  }

  public Direction rotate(int degrees) {
     int length = Direction.values().length;
     int index = (ordinal() + (degrees / (DEGREES / length))) % length;
     return Direction.values()[index];
  } 
}

如您所见,我正在使用 ordinal() 以便我可以循环浏览方向(并返回下一个相关方向)。例如。从 NORTH 旋转 90 度将返回 EAST。

但是我知道使用序数并不是一个好习惯,所以我想知道是否有更好的方法来做到这一点,同时仍然保持代码大小和可读性。

如果您有任何建议,我将不胜感激!

【问题讨论】:

    标签: java enums ordinal


    【解决方案1】:

    如果将来有人引入新成员是枚举,您的代码将会中断,例如 WEST_FAR_AWAY(-10, 0)。您需要根据您的应用程序的要求来估计这是一个“轻微且可接受的缺陷”还是“维护噩梦”......

    【讨论】:

    • 我在这里争辩说这是一个小问题,因为这不是枚举的设计目的,而且它在我的代码中有很好的记录。
    • 那就这样吧。如果我们以务实的方式看待它,只要应用程序的其余部分没有“感染”,在一个类中做不灵活的事情并没有那么糟糕。这种情况下,如果以后改变主意,只需要重写一个方法即可。
    • 您可以通过添加适当的单元测试来防止维护噩梦。您还应该添加一个代码注释来解释ordinal() 的使用,这反过来又会使添加新枚举值成为问题。
    • @TomasKarban 好吧,我不认为单元测试或代码 cmets 可以取代好的设计。当你有了好的设计,你就不需要因为一个新的需求而重写很多代码。
    【解决方案2】:

    我想说这是ordinal 的完全可以接受的用法。事实上,在我看来,它非常简洁。

    关于在您的enum 代码中不使用ordinal 的cmets 只是为了阻止您这样做,因为大多数时候您可以使用enum 本身或EnumMap。

    您对EffectiveJava 的引用是在警告您不要这样做,因为选择正确的int 用作数组的偏移量是令人担忧的并且通常是不必要的。在您的情况下不适用,因为 a)您做得正确,b)这是实现您正在寻找的功能的最简单方法。

    【讨论】:

    • 如果您仔细阅读 Effective Java(第 31 条),您会发现实际上它建议不要以这种方式使用序数因为无法将新元素添加到枚举中。在这种情况下,WEST_FAR_AWAY(-10, 0) 元素也会破坏代码。所以不,Joshua Bloch 不会批准这个代码,这是一个快速而肮脏的解决方案。可以(工作),但不是最好的。
    • @ibalazscs - 在这种情况下,添加一个新的enum 是没有意义的。您的 WEST_FAR_AWAY 根本没有意义。
    • 我可以想到许多有意义的场景。例如 move 方法使用偏移量,并且可以使用具有不同偏移量的方向来实现快速移动的东西。或者结果是由于某种原因应该删除 SOUTH_WEST - 这也会破坏这个脆弱的代码。
    • 有人在我建议序数的另一个答案中抨击我,并简单地将“阅读有效 Java”作为论据。我想说最好的“最佳实践”是态势感知和常识。如果枚举仅在运行时代码中使用,您可能不会添加元素(其运行时)。在 db/orm 场景中,您通常希望使用字符串表示,因为它更具可读性。但是,无论如何,有人可以重命名它并炸毁它。令人震惊,但在这里和那里不同意书籍是可以的。
    【解决方案3】:

    可以在我的 Enum 定义中使用它吗?

    可以用。但是您提供的链接中很好地说明了不使用它并使用 EnumMap 的原因(引用已修改,因为它指的是它的部分中描述的示例)

    这个程序有效,甚至可能看起来很优雅,但外表可能具有欺骗性。就像编译器无法知道序数和数组索引之间的关系一样。如果您在某处进行了更改而忘记相应地更新它,您的程序将在运行时失败。

    如果您将 EnumMap 包含在您的设计中以获取方向,那么 EnumMap 似乎非常有效。

    【讨论】:

    • 我不明白你会如何在这个设计中使用 EnumMap?它会是一个 Map (即到下一个方向)。在示例中,他们有使用 Type 枚举并将 Type 存储到实际 Herbs 集合的草药。我只对从当前方向找到度数/ 45(在我的情况下)的方向感兴趣。我想不出你会如何为它定义一个有用的 EnumMap。
    • 是的,正确。它不在类似的路线上,我也发现很难整合。因为您需要在旋转 180* 后获得所有度数的方向,例如西应该给东。这是一个更像 1..n 的映射。所以当前的设计很好,但在引入更改时必须小心,因为这可能会导致复杂且不易追踪的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-17
    • 2018-10-20
    • 1970-01-01
    相关资源
    最近更新 更多