使用枚举的序数是一种好习惯吗?
Posted
技术标签:
【中文标题】使用枚举的序数是一种好习惯吗?【英文标题】:Is it good practice to use ordinal of enum? 【发布时间】:2017-11-23 01:41:32 【问题描述】:我有一个枚举:
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;
【问题讨论】:
你还需要一个数值吗?枚举值可以直接相互比较。例如,if (Persons.CHILD.compareTo(Persons.PARENT) < 0) System.out.println("CHILD has a smaller value than PARENT.");
“枚举的序数”?那将是枚举。
人们经常做这种事情,但它使代码维护更加困难。
也许这根本不是枚举的工作。
这里没有理由使用Integer
对象而不是int
值。
【参考方案1】:
TLDR:不,你不应该!
如果你参考Enum.java
中ordinal
方法的javadoc:
大多数程序员都不会使用这种方法。它是 设计用于复杂的基于枚举的数据结构,例如 如
java.util.EnumSet
和java.util.EnumMap
。
首先 - 阅读手册(在本例中为 javadoc)。
其次 - 不要编写脆弱的代码。枚举值将来可能会发生变化,您的第二个代码示例更加清晰和可维护。
如果(比如说)在PARENT
和GRANDPARENT
之间插入一个新的枚举值,您绝对不想为将来制造问题。
【讨论】:
在列表中插入新项目时,必须手动重新编号所有后续项目如何更易于维护? @DavidMoles 它可能不是最可维护的,但它显然比让你的行为取决于你碰巧声明枚举值的顺序要好。跨度> @DavidMoles 因为唯一不那么可维护的是代码,如果有人以错误的方式更改它,代码就会中断,而这并不能解释这一事实。虽然就个人而言,我会使用第一种方法并添加注释来解释顺序很重要...... @nhouser9 ...如果有人忽略评论并更改顺序,一组测试将失败。如果您的团队不幸处于不运行单元测试或经常忽略故障的团队中,那么明确编号的实施会更安全。 @ChrisHayes 很多事情取决于您声明枚举值的顺序,包括compareTo()
,即final
。顺序对于 Java 枚举是有意义的。如果你想让它没有意义,你应该用你自己的排序规则编写你自己的类枚举类。【参考方案2】:
第一种方法并不容易理解,因为您必须阅读使用枚举的代码才能了解枚举的顺序很重要。 这很容易出错。
public enum Persons
CHILD,
PARENT,
GRANDPARENT;
第二种方式更好,因为它不言自明:
CHILD(0),
PARENT(1),
GRANDPARENT(2);
private SourceType(final Integer hierarchy)
this.hierarchy = hierarchy;
当然,枚举值的顺序应该与枚举构造函数参数提供的层次顺序一致。
它引入了一种冗余,因为枚举值和枚举构造函数的参数都传达了它们的层次结构。但为什么会有问题呢?枚举旨在表示恒定且不经常变化的值。 OP 枚举的用法很好地说明了一个很好的枚举用法:
CHILD, PARENT, GRANDPARENT
枚举并非旨在表示频繁移动的值。 在这种情况下,使用枚举可能不是最佳选择,因为它可能会频繁破坏使用它的客户端代码,而且每次修改枚举值时都会强制重新编译、重新打包和重新部署应用程序。
【讨论】:
这是一个很好的观点,但我认为只需添加一个注释说明标识符的顺序很重要。 除了第二种方式真的不言自明。 @Robin Davies 你指的hierarchy
字段名称可能会产生误导?【参考方案3】:
正如 Joshua Bloch 在 Effective Java 中所建议的那样,从枚举的序数中派生与枚举关联的值并不是一个好主意,因为更改枚举值的顺序可能会破坏您的逻辑编码。
您提到的第二种方法完全遵循作者的建议,即将值存储在单独的字段中。
我会说您建议的替代方案肯定更好,因为它更具可扩展性和可维护性,因为您正在解耦枚举值的顺序和层次结构的概念。
【讨论】:
【参考方案4】:首先,您可能甚至不需要数字顺序值——那就是
什么Comparable
用于,Enum<E>
实现 Comparable<E>
。
如果您出于某种原因确实需要数字顺序值,是的,您应该
使用ordinal()
。这就是它的用途。
Java Enums
的标准做法是按声明顺序排序,
这就是为什么Enum<E>
实现Comparable<E>
以及为什么
Enum.compareTo()
是 final
。
如果您添加自己不使用的非标准比较代码
Comparable
不依赖于申报顺序,你只是
会混淆任何试图使用您的代码的其他人,包括
你自己未来的自己。没有人会期望该代码存在。
他们会期望Enum
是Enum
。
如果自定义顺序与申报顺序不符,任何人 看着声明会很困惑。如果它确实 (恰好,此时)符合申报顺序,任何人 看着它会变得期待,他们会 当在将来的某个日期没有时,会感到非常震惊。 (如果你写 代码(或测试)以确保自定义顺序与 声明令,你只是在强调它是多么不必要。)
如果您添加自己的订单价值,您将面临维护难题 为自己:
-
您需要确保您的
hierarchy
值是唯一的
如果中间加一个值,需要全部重新编号
后续值
如果您担心有人会在 将来,编写一个检查顺序的单元测试。
总之,用Item 47的不朽的话来说: 了解和使用库。
附:另外,当您的意思是int
时,不要使用Integer
。 ?
【讨论】:
枚举字段可以用于除了比较和排序之外的其他事情。 @LeonardoPina 是的,但这不是 OP 要求的。 公平地说,尚不清楚 OP 想要对“层次结构”做什么。 这不是一个写得特别好的问题,但它明确谈到的唯一用途是顺序,并且给出的示例完全硬编码了ordinal()
将给出的相同值。
@Davide Moles。谢谢你。在令人讨厌和适得其反的传说的海洋中发出理性的声音。人们总是对在这个问题上给出的那种建议感到困惑。我花了两天时间思考,最终得出了与您相同的结论。当我发现你的帖子时,我正在回去写一个反对在这种情况下使用枚举字段的论点。要是我两天前就注意到了就好了。【参考方案5】:
如果你只想创建枚举值之间的关系,你实际上可以使用使用其他枚举值的技巧:
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;
请注意,您只能使用在您尝试声明的枚举值之前按词法声明的枚举值,因此这仅在您的关系形成无环有向图时才有效(并且您声明它们的顺序是有效的拓扑排序) .
【讨论】:
【参考方案6】:不推荐使用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
,例如
【讨论】:
【参考方案7】:我会使用您的第二个选项(使用显式整数),因此数值由您而不是 Java 分配。
【讨论】:
【参考方案8】:根据javadoc
返回这个枚举常量的序数(它在它的位置 枚举声明,其中初始常量被分配一个序数 零)。大多数程序员不会使用这种方法。它是 设计用于复杂的基于枚举的数据结构,例如 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
。
【讨论】:
【参考方案9】:这不是您问题的直接答案。为您的用例提供更好的方法。这样可以确保下一个开发人员明确知道分配给属性的值不应更改。
创建一个具有静态属性的类来模拟您的枚举:
public class Persons
final public static int CHILD = 0;
final public static int PARENT = 1;
final public static int GRANDPARENT = 2;
然后像枚举一样使用:
Persons.CHILD
它适用于大多数简单的用例。否则,您可能会缺少 valueOf()、EnumSet、EnumMap 或 values() 等选项。
【讨论】:
最大的缺点是方法声明的参数是 int 类型的,并且不再有编译器防止传递非法值的保护。为了避免使用序数的令人难以置信的微妙缺点,这似乎是一个不成比例的代价。 IMO,那是枚举存在【参考方案10】:让我们考虑以下示例:
我们需要在 Spring 应用程序中订购几个过滤器。这可以通过 FilterRegistrationBeans 注册过滤器来实现:
@Bean
public FilterRegistrationBean compressingFilterRegistration()
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(compressingFilter());
registration.setName("CompressingFilter");
...
registration.setOrder(1);
return registration;
假设我们有几个过滤器,我们需要指定它们的顺序(例如,我们希望首先设置为所有记录器添加 MDC 上下文的过滤器)
在这里我看到了ordinal()
的完美用例。让我们创建枚举:
enum FilterRegistrationOrder
MDC_FILTER,
COMPRESSING_FILTER,
CACHE_CONTROL_FILTER,
SPRING_SECURITY_FILTER,
...
现在在注册 bean 中我们可以使用:
registration.setOrder(MDC_FILTER.ordinal());
它在我们的案例中完美运行。如果我们没有枚举来执行此操作,我们将不得不通过向它们(或存储它们的常量)添加 1 来重新枚举所有过滤器顺序。当我们有枚举时,您只需在枚举中的适当位置添加一行并使用序数。我们不必在很多地方更改代码,并且我们在一个地方为所有过滤器提供了清晰的顺序结构。
在这种情况下,我认为ordinal()
方法是以干净和可维护的方式实现过滤器顺序的最佳选择
【讨论】:
【参考方案11】:您必须根据自己的判断来评估在您的特定情况下哪种错误会更严重。这个问题没有万能的答案。每个解决方案都利用了编译器的一个优势,但牺牲了另一个。
如果你最糟糕的噩梦是枚举偷偷改变值:使用ENUM(int)
如果您最糟糕的噩梦是枚举值重复或失去连续性:使用ordinal
。
【讨论】:
以上是关于使用枚举的序数是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章