【问题标题】: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 值。

标签: java enums coding-style verbose


【解决方案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

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

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

    1. 您需要确保您的 hierarchy 值是唯一的
    2. 如果中间加一个值,需要全部重新编号 后续值

    如果您担心有人会在 将来,编写一个检查顺序的单元测试。

    总之,用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()EnumSetEnumMapvalues() 等选项。

            【讨论】:

            • 最大的缺点是方法声明的参数是 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

              【讨论】:

                猜你喜欢
                • 2013-08-03
                • 1970-01-01
                • 2013-04-07
                • 1970-01-01
                • 2015-02-18
                • 2014-07-31
                • 2019-03-06
                • 2013-03-30
                • 2015-09-02
                相关资源
                最近更新 更多