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

时间:2017-06-20 13:21:06

标签: java enums coding-style verbose

我有一个枚举:

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;
    }

}

10 个答案:

答案 0 :(得分:39)

如果您在ordinal中引用Enum.java方法的javadoc:

  

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

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

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

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

答案 1 :(得分:10)

第一种方式不能直接理解,因为您必须阅读使用枚举的代码,以了解枚举的顺序是否重要。
这很容易出错。

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}

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

CHILD(0),
PARENT(1),
GRANDPARENT(2);

private SourceType(final Integer hierarchy) {
    this.hierarchy = hierarchy;
}

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

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

CHILD, PARENT, GRANDPARENT

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

答案 2 :(得分:7)

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

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

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

答案 3 :(得分:6)

首先,您可能甚至不需要数字订单值 - 那就是 什么Comparable 用于,Enum<E>实现Comparable<E>

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

Java Enums的标准做法是按声明顺序排序, 这就是Enum<E>实现Comparable<E>的原因以及原因 Enum.compareTo()final

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

如果自定义订单与声明订单不匹配,则任何人 看着宣言会很困惑。如果它 (碰巧,此时此刻)与声明令相匹配,任何人 看着它会有所期待,而且他们会去 如果在未来的某个日期它没有,那就会受到惊吓。 (如果你写 代码(或测试)确保自定义订单匹配 声明顺序,你只是强化它是多么不必要。)

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

  1. 您需要确保hierarchy值是唯一的
  2. 如果您在中间添加值,则需要重新编号 后续价值
  3. 如果您担心有人可能会意外更改订单 未来,写一个检查订单的单元测试。

    总而言之,在Item 47的不朽话语中: 知道并使用库

    P.S。另外,如果您的意思是Integer,请不要使用int

答案 4 :(得分:5)

由于枚举声明中的更改可能会影响序数值,因此建议不要使用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,例如

答案 5 :(得分: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 :(得分:3)

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

答案 7 :(得分:1)

根据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

答案 8 :(得分:1)

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

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

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()EnumSetEnumMapvalues()等选项。

答案 9 :(得分:0)

让我们考虑以下示例:

我们需要在Spring应用程序中订购几个过滤器。这可以通过FilterRegistrationBeans注册过滤器来实现:

 @Bean
  public FilterRegistrationBean compressingFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(compressingFilter());
    registration.setName("CompressingFilter");
    ...
    registration.setOrder(1);
    return registration;
  }

假设我们有几个过滤器,我们需要指定它们的顺序(例如,我们要首先设置为所有记录器添加MDC上下文JSID的过滤器)

在这里,我看到了ordinal()的完美用例。让我们创建一个枚举:

   enum FilterRegistrationOrder {
    MDC_FILTER,
    COMPRESSING_FILTER,
    CACHE_CONTROL_FILTER,
    SPRING_SECURITY_FILTER,
    ...
    }

现在在注册bean中,我们可以使用: registration.setOrder(MDC_FILTER.ordinal());

在我们的情况下,它非常有效。如果没有枚举,则必须对所有过滤器订单加1(或向存储它们的常数添加)以重新计算其数量。当我们有枚举时,您只需要在枚举的适当位置添加一行并使用序数即可。我们不必在很多地方更改代码,而且我们所有过滤器的顺序结构都清晰易懂。

在这种情况下,我认为ordinal()方法是以干净且可维护的方式实现过滤器顺序的最佳选择