使用枚举序数是一种好习惯吗?

我有枚举:

public enum Persons { CHILD, PARENT, GRANDPARENT; } 

使用ordinal()方法检查枚举成员之间的“层次结构”是否有任何问题? 我的意思是 – 使用它除了冗长之外是否有任何缺点,当有人可能在将来意外改变时。

或者做这样的事情会更好:

 public enum Persons { CHILD(0), PARENT(1), GRANDPARENT(2); private Integer hierarchy; private Persons(final Integer hierarchy) { this.hierarchy = hierarchy; } public Integer getHierarchy() { return hierarchy; } } 

如果在Enum.java引用javadoc for ordinal方法:

大多数程序员都没有使用这种方法。 它设计用于复杂的基于枚举的数据结构,例如java.util.EnumSetjava.util.EnumMap

首先 – 阅读手册(本例中为javadoc)。

其次 – 不要写脆弱的代码。 枚举值可能在将来发生变化,您的第二个代码示例更加清晰和可维护

如果在PARENTGRANDPARENT之间插入一个新的枚举值,你肯定不希望为将来创建问题。

第一种方式是不可理解的,因为你必须阅读使用枚举的代码来理解枚举的顺序很重要。
这很容易出错。

 public enum Persons { CHILD, PARENT, GRANDPARENT; } 

第二种方式更好,因为它是自我解释的

 CHILD(0), PARENT(1), GRANDPARENT(2); private SourceType(final Integer hierarchy) { this.hierarchy = hierarchy; } 

当然,枚举值的顺序应该与枚举构造函数参数提供的层次顺序一致。

它引入了一种冗余,因为枚举值和枚举构造函数的参数都传达了它们的层次结构。
但为什么会出现问题呢?
枚举旨在表示常量且不经常更改的值
OP枚举用法说明了良好的枚举用法:

 CHILD, PARENT, GRANDPARENT 

枚举不是为了表示经常移动的值。
在这种情况下,使用枚举可能不是最佳选择,因为它可能经常破坏使用它的客户端代码,并且除了它强制在每次修改枚举值时重新编译,重新打包和重新部署应用程序。

首先,您可能甚至不需要数字顺序值 – 这就是Comparable的用途,而Enum实现了Comparable

如果由于某种原因确实需要数字订单值,是的,您应该使用ordinal() 。 这就是它的用途。

Java Enums标准实践是按声明顺序排序,这就是为什么Enum实现Comparable以及为什么Enum.compareTo()final

如果您添加自己的非标准比较代码,该代码不使用Comparable并且不依赖于声明顺序,那么您只会混淆任何试图使用您的代码的人,包括您自己的未来。 没有人会期望代码存在; 他们期望Enum成为Enum

如果自定义订单与声明订单不匹配,那么查看声明的任何人都会感到困惑。 如果它确实 (碰巧,在这个时刻)与声明顺序相匹配,任何看着它的人​​都会期待这一点,而且在未来的某个日期它们不会受到令人讨厌的冲击。 (如果您编写代码(或测试)以确保自定义订单与声明顺序匹配,那么您只需加强其不必要的程度。)

如果您添加自己的订单值,则会为您自己创建维护问题:

  1. 您需要确保您的hierarchy值是唯一的
  2. 如果在中间添加值,则需要重新编号所有后续值

如果您担心将来可能会意外更改订单,请编写一个检查订单的unit testing。

总而言之,在第47项的不朽的话语中: 了解并使用这些库


PS另外,当你的意思是int时,不要使用Integer 。 🙂

正如Joshua Bloch在Effective Java中所建议的那样,从序数中导出与枚举相关联的值并不是一个好主意,因为对枚举值排序的更改可能会破坏您编码的逻辑。

您提到的第二种方法完全遵循作者提出的建议,即将值存储在单独的字段中。

我会说你建议的替代方案肯定更好,因为它更加可扩展和可维护,因为你正在解耦枚举值的排序和层次结构的概念。

使用ordinal()是不推荐的,因为枚举声明中的更改可能会影响序数值。

更新:

值得注意的是,枚举字段是常量并且可以具有重复的值,即

 enum Family { OFFSPRING(0), PARENT(1), GRANDPARENT(2), SIBLING(3), COUSING(4), UNCLE(4), AUNT(4); private final int hierarchy; private Family(int hierarchy) { this.hierarchy = hierarchy; } public int getHierarchy() { return hierarchy; } } 

根据您计划对hierarchy执行的操作,这可能是有害的或有益的。

此外,您可以使用枚举常量来构建您自己的EnumFlags而不是使用EnumSet

如果您只想在枚举值之间创建关系,您实际上可以使用其他枚举值的技巧:

 public enum Person { GRANDPARENT(null), PARENT(GRANDPARENT), CHILD(PARENT); private final Person parent; private Person(Person parent) { this.parent = parent; } public final Parent getParent() { return parent; } } 

请注意,您只能使用在您尝试声明的枚举值之前声明的枚举值,因此这仅适用于您的关系形成非循环有向图(并且您声明它们的顺序是有效的拓扑排序)。

我将使用您的第二个选项(使用显式整数),因此数值由您分配,而不是由Java分配。

根据java doc

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

您可以通过更改枚举的顺序来控制序号,但不能明确地设置它。一种解决方法是在枚举中为您想要的数字提供额外的方法。

 enum Mobile { Samsung(400), Nokia(250),Motorola(325); private final int val; private Mobile (int v) { val = v; } public int getVal() { return val; } } 

在这种情况下, Samsung.ordinal() = 0 ,但Samsung.getVal() = 400

这不是您问题的直接答案。 更好的方法来处理你的用例。 这样可以确保下一个开发人员明确知道不应更改分配给属性的值。

创建一个具有静态属性的类,它将模拟您的枚举:

 public class Persons { final public static int CHILD = 0; final public static int PARENT = 1; final public static int GRANDPARENT = 2; } 

然后像enum一样使用:

 Persons.CHILD 

它适用于大多数简单的用例。 否则,您可能会缺少valueOf() , EnumSet , EnumMap或values()等选项。