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

Is it good practice to use ordinal of enum?

我有一个枚举:

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}

使用ordinal()方法检查枚举成员之间的"hierarchy"有什么问题吗?我的意思是 - 使用它时是否有任何缺点,除了冗长,当有人将来可能会意外更改顺序时。

还是这样做更好:

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

}

我会使用你的第二个选项(使用明确的整数),所以数值是由你而不是 Java 分配的。

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

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}

第二种方式更好,因为它不言自明

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

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

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

它引入了一种冗余,因为枚举值和枚举构造函数的参数都传达了它们的层次结构。
但是为什么会有问题呢?
枚举旨在表示常量而不是经常变化的值
OP 枚举用法很好地说明了一个很好的枚举用法:

CHILD, PARENT, GRANDPARENT

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

不推荐使用 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,例如

TLDR:不,你不应该!

如果您在 Enum.java 中参考 ordinal 方法的 javadoc:

Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as java.util.EnumSet and java.util.EnumMap.

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

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

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

根据javadoc

Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero). Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap.

您可以通过更改枚举的顺序来控制序数,但您不能设置它explicitly.One解决方法是在您的枚举中为您想要的数字提供一个额外的方法。

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

首先,您可能甚至不需要数字订单值——那是 什么 Comparable 是为了 Enum<E> 实现 Comparable<E>.

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

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

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

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

如果您添加自己的订单价值,就会造成维护上的麻烦 为你自己:

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

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

总而言之,Item 47的不朽名言: 了解并使用库


P.S。另外,当你的意思是 int 时,不要使用 Integer

如果您只想在枚举值之间创建关系,您实际上可以使用 其他枚举值:

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

请注意,您只能使用在您尝试声明的值之前按词法声明的枚举值,因此这仅在您的关系形成非循环有向图(并且您声明它们的顺序是有效的拓扑)时才有效排序)。

正如 Joshua Bloch 在 Effective Java 中所建议的那样,从枚举的序数中导出与枚举关联的值并不是一个好主意,因为顺序会发生变化枚举值的数量可能会破坏您编码的逻辑。

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

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

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

创建一个具有静态属性的 class 来模拟您的枚举:

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 or values().

这样的选项

让我们考虑以下示例:

我们需要在 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() 方法是以干净且可维护的方式实现过滤器顺序的最佳选择

您必须根据自己的判断来评估在您的特定情况下哪种错误会更严重。这个问题没有千篇一律的答案。每个解决方案都利用了编译器的一个优势,但牺牲了另一个优势。

如果你最糟糕的噩梦是偷偷改变值的枚举:使用ENUM(int)

如果你最糟糕的噩梦是枚举值变得重复或失去连续性:使用 ordinal.