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

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.javaordinal方法的javadoc:

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

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

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

如果(比如说)在PARENTGRANDPARENT 之间插入一个新的枚举值,您绝对不想为将来制造问题。

【讨论】:

在列表中插入新项目时,必须手动重新编号所有后续项目如何更易于维护? @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 不依赖于申报顺序,你只是 会混淆任何试图使用您的代码的其他人,包括 你自己未来的自己。没有人会期望该代码存在。 他们会期望EnumEnum

如果自定义顺序与申报顺序不符,任何人 看着声明会很困惑。如果它确实 (恰好,此时)符合申报顺序,任何人 看着它会变得期待,他们会 当在将来的某个日期没有时,会感到非常震惊。 (如果你写 代码(或测试)以确保自定义顺序与 声明令,你只是在强调它是多么不必要。)

如果您添加自己的订单价值,您将面临维护难题 为自己:

    您需要确保您的 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

【讨论】:

以上是关于使用枚举的序数是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用引用来布置简单的功能是一种好习惯吗

在“for”循环条件中使用“三元运算”是一种好习惯吗?

将对象文字用作哈希表是一种好习惯吗?

登录后重新生成会话 ID 是一种好习惯吗?

尝试使用资源文件编写器是一种好习惯吗

在 perl 中使用嵌套映射是一种好习惯吗?