42

我有一个枚举:

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

}
4

11 回答 11

66

TLDR:不,你不应该!

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

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

首先 - 阅读手册(在这种情况下为 javadoc)。

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

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

于 2017-06-20T13:27:02.340 回答
15

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

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}

第二种方法更好,因为它是不言自明的:

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

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

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

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

CHILD, PARENT, GRANDPARENT

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

于 2017-06-20T13:24:08.093 回答
15

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

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

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

于 2017-07-31T04:42:05.630 回答
11

首先,您可能甚至不需要数字顺序值——这Comparable 就是 for 和Enum<E>implements的用途Comparable<E>

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

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

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

如果自定义顺序与声明顺序不匹配,任何查看声明的人都会感到困惑。如果它确实 (此时恰好)符合声明顺序,那么任何看到它的人都会预料到这一点,并且如果在未来的某个日期不符合,他们会感到非常震惊。(如果您编写代码(或测试)以确保自定义顺序与声明顺序相匹配,那么您只是在强调它是多么不必要。)

如果您添加自己的订单价值,您就会为自己制造维护难题:

  1. 你需要确保你的hierarchy价值观是独一无二的
  2. 如果在中间添加一个值,则需要对所有后续值重新编号

如果您担心将来有人会意外更改订单,请编写一个检查订单的单元测试。

总之,用第 47 条的不朽名言: 知道和使用图书馆


IntegerPS另外,当你的意思是不要使用int

于 2017-06-20T16:45:05.057 回答
7

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

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

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

于 2017-06-20T17:06:00.543 回答
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,例如

于 2017-06-20T13:25:52.473 回答
3

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

于 2017-06-20T13:22:55.997 回答
1

根据java文档

返回此枚举常量的序号(它在其枚举声明中的位置,其中初始常量的序号为零)。大多数程序员不会使用这种方法。它设计用于复杂的基于枚举的数据结构,例如 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

于 2017-06-20T13:43:55.617 回答
1

这不是您问题的直接答案。为您的用例提供更好的方法。这种方式确保下一个开发人员将明确知道分配给属性的值不应更改。

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

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

于 2017-10-26T08:15:39.073 回答
1

让我们考虑以下示例:

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

于 2020-11-12T14:39:08.780 回答
0

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

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

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

于 2021-09-15T10:38:12.087 回答