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

Bye*_*Bye 26 java enums coding-style verbose

我有一个枚举:

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

vik*_*eve 44

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

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

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

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

如果在(PARENT和)之间插入一个新的枚举值,你绝对不希望为将来创建问题GRANDPARENT.

  • 当您在列表中插入新项目更易于维护时,如何手动重新编号所有后续项目? (12认同)
  • @ nhouser9 ...以及一组如果有人忽略评论并改变顺序将失败的测试.如果您处于一个不幸的位置,即团队中没有运行单元测试,或者经常忽略故障,那么明确编号的实现会更加安全. (6认同)
  • @DavidMoles它可能不是*最可维护的,但它明显优于你的行为取决于你恰好声明你的枚举值的顺序. (5认同)
  • @ChrisHayes许多事情取决于在你碰巧声明了枚举值,包括`的compareTo()`的顺序,这是'final`上.订单对Java枚举有意义.如果您希望它没有意义,您应该使用自己的排序规则编写自己的类枚举类. (4认同)
  • @DavidMoles因为唯一不太可维护的是代码,如果有人以错误的方式改变它将会破坏,并且没有任何东西可以解释这个事实.虽然我个人而言,但我会使用第一种方法并添加一条评论来解释该订单的重要性...... (3认同)
  • @DavidMoles - 好问题。由于您明确声明了“层次结构”,因此很明显您将一个“整数”与每个枚举相关联。最重要的是,如果枚举以某种方式重新排序(例如,有人在 `PARENT` 和 `GRANDPARENT` 之间的线上添加了 `GUARDIAN`),那么最后一个枚举值的值不会改变。 (2认同)
  • 有很多情况需要避免使用序数,并且由于在任何上下文中“序数”的固有歧义,通常是一个维护问题。Joshua Bloch 在 Effective Java 中讨论了这一点,以及许多学术文章/论文 (2认同)

dav*_*xxx 10

第一种方式是不可理解的,因为你必须阅读使用枚举的代码来理解枚举的顺序很重要.
这很容易出错.

public enum Persons {

    CHILD,
    PARENT,
    GRANDPARENT;

}
Run Code Online (Sandbox Code Playgroud)

第二种方式更好,因为它是自我解释的:

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

private SourceType(final Integer hierarchy) {
    this.hierarchy = hierarchy;
}
Run Code Online (Sandbox Code Playgroud)

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

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

CHILD, PARENT, GRANDPARENT
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一个好点,但我认为简单地添加一个注释,标识符的顺序是重要的也是一样好. (3认同)

Dav*_*les 7

首先,您可能甚至不需要数字订单值 - 这就是为什么ComparableEnum<E>实现Comparable<E>.

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

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

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

如果自定义订单与声明订单不匹配,那么查看声明的任何人都会感到困惑.如果它确实 (碰巧,此时此刻)与声明顺序相符,那么任何看着它的人​​都会期待这一点,而且在未来的某个日期,它会发生令人讨厌的震惊.(如果您编写代码(或测试)以确保自定义订单与声明顺序匹配,那么您只需加强其不必要的程度.)

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

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

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

总而言之,在第47项的不朽的话语中: 知道并使用这些库.


PS此外,不要Integer在你的意思使用int.

  • @大卫·莫尔斯。谢谢。在令人厌恶和适得其反的知识海洋中发出理性的声音。人们总是对针对这个问题给出的建议感到困惑。我思考了两天,最后得出的结论和你一样。当我发现你的帖子时,我正在回来写一篇反对在这种情况下使用枚举字段的论点。要是我两天前就注意到就好了。 (2认同)

msf*_*013 7

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

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

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


Leo*_*ina 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

根据您计划要做的事情,hierarchy这可能是有害的或有益的.

此外,您可以使用枚举常量来构建您自己的常量EnumFlags而不是使用EnumSet,例如


Dan*_*den 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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