【问题标题】:Advantages of Java's enum over the old "Typesafe Enum" pattern?Java 的枚举相对于旧的“类型安全枚举”模式的优势?
【发布时间】:2011-07-02 19:08:04
【问题描述】:

在 JDK1.5 之前的 Java 中,“Typesafe Enum”模式是实现只能采用有限数量值的类型的常用方法:

public class Suit {
    private final String name;

    public static final Suit CLUBS =new Suit("clubs");
    public static final Suit DIAMONDS =new Suit("diamonds");
    public static final Suit HEARTS =new Suit("hearts");
    public static final Suit SPADES =new Suit("spades");    

    private Suit(String name){
        this.name =name;
    }
    public String toString(){
        return name;
    }
}

(参见例如 Bloch 的 Effective Java 中的第 21 项)。

现在在JDK1.5+中,“官方”的方式显然是使用enum

public enum Suit {
  CLUBS("clubs"), DIAMONDS("diamonds"), HEARTS("hearts"), SPADES("spades");

  private final String name;

  private Suit(String name) {
    this.name = name;
  }
}

显然,语法更好更简洁(无需显式定义值的字段,提供合适的toString()),但到目前为止enum 看起来非常像Typesafe Enum 模式。

我知道的其他差异:

  • 枚举自动提供values() 方法
  • 枚举可用于switch()(编译器甚至会检查您是否忘记了某个值)

但这一切看起来只不过是语法糖,甚至还有一些限制(例如,enum 总是继承自 java.lang.Enum,并且不能被子类化)。

enum 提供的其他更基本的好处是否无法通过 Typesafe Enum 模式实现?

【问题讨论】:

  • 真正的优势:序列化速度更快。其余的可以模拟。
  • @sleske 编译器在 switch 中检查是什么意思。我没听说过这个功能。最好不要使用 switch 而是为抽象方法的每个枚举值实现一个具体的方法。
  • @user1198898:如果您在开关中重复case,编译器将显示错误,并且如果不是所有枚举值都被案例覆盖,它会发出警告。是的,通常使用多态性而不是开关会更好,但这取决于具体情况。
  • @sleske 感谢您清除它。

标签: java design-patterns enums language-design


【解决方案1】:

现在在JDK1.5+中,“官方”的方式是 显然要使用enum:

public enum Suit {
  CLUBS("clubs"), DIAMONDS("diamonds"), HEARTS("hearts"), SPADES("spades");

  private final String name;

  private Suit(String name) {
    this.name = name;
  }
}

其实更像

 public enum Suit {
     CLUBS, DIAMONDS, HEARTS, SPADES;
 }

因为枚举已经提供了name() 方法。此外,它们还提供了ordinal() 方法(它可以实现EnumSetEnumMap 等高效数据结构),实现Serializable,覆盖toString,提供values()valueOf(String name)。它们可以在类型安全的 switch 语句中使用,并且是单例。

【讨论】:

    【解决方案2】:

    语法糖本身就物有所值:-P 毕竟,对于 ( : ) 也是如此。

    但是说真的,开箱即用的自动 name() 和 ordinal()、枚举它们、在 switch() 中使用它们、为它们附加附加值的事实对它们来说是很好的论据:它避免了很多样板代码。

    使用 int 的传统惰性替代方法不是类型安全的,而且受到更多限制。 与这种替代方法相比,枚举的一个缺点是它们不再是轻量级的。

    【讨论】:

    • 实际上,我相信枚举是相当轻量级的。特别是,如果您实际使用它们的数量很重要,您可以使用轻量级的 EnumSet 和 EnumMap。
    【解决方案3】:

    另外:

    JDK5 枚举可以轻松用于具有良好 IDE 支持的 switch-case 语句

    Suit suit = ...; 
    switch (suit) { 
        case SPADES: System.out.println("Motorhead!"); break;
        default: System.out.println("Boring ..");
    }
    

    【讨论】:

      【解决方案4】:

      当然,其他人会在这里提到很多优点作为答案。最重要的是,您可以非常快速地编写enums,并且他们会执行很多操作,例如实现SerializableComparableequals()toString()hashCode() 等,这些您没有包含在您的枚举。

      但我可以向您展示enum (IMO) 的严重缺点。不仅不能随意对它们进行子类化,而且不能为它们配备通用参数。什么时候可以这样写:

      // A model class for SQL data types and their mapping to Java types
      public class DataType<T> {
          private final String name;
          private final Class<T> type;
      
          public static final DataType<Integer> INT      = new DataType<Integer>("int", Integer.class);
          public static final DataType<Integer> INT4     = new DataType<Integer>("int4", Integer.class);
          public static final DataType<Integer> INTEGER  = new DataType<Integer>("integer", Integer.class);
          public static final DataType<Long>    BIGINT   = new DataType<Long>("bigint", Long.class);    
      
          private DataType(String name, Class<T> type){
              this.name = name;
              this.type = type;
          }
      
          // Returns T. I find this often very useful!
          public T parse(String string) throws Exception {
              // [...]
          }
      }
      
      class Utility {
      
          // Enums equipped with generic types...
          public static <T> T doStuff(DataType<T> type) {
              return ...
          }
      }
      

      使用枚举是不可能的:

      // This can't be done
      public enum DataType<T> {
      
          // Neither can this...
          INT<Integer>("int", Integer.class), 
          INT4<Integer>("int4", Integer.class), 
      
          // [...]
      }
      

      【讨论】:

      • 我明白你的意思,但我认为这个例子不是一个好例子。为什么我要将Club 作为与Diamond 分开的类型。在我看来,它们是同一类型的完全不同的值,而不是单独的类型。
      • 你是对的。这个例子真的只是一个例子。我会尝试展示一个现实世界的问题..
      • 你可以继承一个枚举,它只需要作为一个匿名类型发生。其他任何事情都会打破规范所做的假设。
      • @josefx,你能举个例子吗?如何子类化枚举? Enum 不能被子类化,编译器会阻止它。而具体的enum 类型也不能被子类化,因为enum 中的所有构造函数都必须是私有的......
      • @Lukas Eder enum AnEnum{HELLO(){public void print(){System.out.println("Hello");}}, WORLD(){public void print(){System. out.println("世界");}}; public abstract void print();} ____________ HELLO 和 WORLD 值都作为 AnEnum 的子类实现。
      【解决方案5】:

      EnumSetEnumMap 是围绕枚举的特定特性构建的自定义数据结构。它们有方便的额外功能,而且速度非常快。没有枚举,就没有等价物(至少没有等价的使用优雅,请参阅 cmets)。

      【讨论】:

      • A BitSetEnumSet 的一个非常有效的替代品 iff 您将 enum 替换为 int 值而不是类型安全的枚举模式实现。跨度>
      • @Joachim 授予。但 API 更难看,EnumSet 有很好的工厂方法:copyOf()complementOf() 等。
      • 我同意EnumSet 更好。我只是不同意“没有等价物”。
      • 您可以在您的“类型安全枚举类”中添加一个ordinal() 方法,然后基于它实现大部分其余部分。但可以肯定的是,enum 提供了一种更好的编写方式,并且所有基础设施都已经存在。
      【解决方案6】:

      您的类型安全枚举实现有点过于简单。当您处理序列化时,它会变得更加复杂。

      Java enums 解决了序列化/反序列化的问题。 enum 保证是唯一的,您可以将它们与 == 运算符进行比较。

      阅读 Effective Java 2nd Edition 中的相应章节(关于使用枚举而不是单例,关于使用 EnumSets 等)。

      【讨论】:

      • 使用枚举而不是单例?每个枚举项一个单例!!
      • @Sean Patrick Floyd:是的,但它不止于此(这就是重点)。
      【解决方案7】:
      • “不能被子类化”不是限制。这是一大优势:它确保始终只有在 enum 中定义的一组值,仅此而已!
      • enum 正确处理序列化。您可以使用类型安全的枚举来做到这一点,但它经常被遗忘(或根本不知道)。这确保了e1.equals(e2) 总是 暗示e1 == e2 对任意两个enume1e2(反之亦然,这可能更重要)。
      • 有一些特定的轻量级数据结构可以处理枚举:EnumSetEnumMap(从 this answer 窃取)

      【讨论】:

      • 我不认为你可以继承一个只有私有构造函数的类...除了嵌套在类本身中。
      • @Mark: 是的,但您可以修改类以使用 protected 构造函数。
      • 是的,我认为构造函数应该是私有的,并且类应该是最终的。
      • +1 序列化。它也像你可以在运行时向枚举添加值一样铁定。我确实希望枚举的每个值都是最终的子类,但我想你不能同时拥有两者(可以吗?)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      相关资源
      最近更新 更多