使用枚举序数是一种好习惯吗?
我有枚举:
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.EnumSet
和java.util.EnumMap
。
首先 – 阅读手册(本例中为javadoc)。
其次 – 不要写脆弱的代码。 枚举值可能在将来发生变化,您的第二个代码示例更加清晰和可维护 。
如果在PARENT
和GRANDPARENT
之间插入一个新的枚举值,你肯定不希望为将来创建问题。
第一种方式是不可理解的,因为你必须阅读使用枚举的代码来理解枚举的顺序很重要。
这很容易出错。
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
。
如果自定义订单与声明订单不匹配,那么查看声明的任何人都会感到困惑。 如果它确实 (碰巧,在这个时刻)与声明顺序相匹配,任何看着它的人都会期待这一点,而且在未来的某个日期它们不会受到令人讨厌的冲击。 (如果您编写代码(或测试)以确保自定义订单与声明顺序匹配,那么您只需加强其不必要的程度。)
如果您添加自己的订单值,则会为您自己创建维护问题:
- 您需要确保您的
hierarchy
值是唯一的 - 如果在中间添加值,则需要重新编号所有后续值
如果您担心将来可能会意外更改订单,请编写一个检查订单的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()等选项。