Java的枚举优于旧的“Typesafe Enum”模式?

时间:2011-02-23 14:12:01

标签: java design-patterns enums language-design

在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模式无法实现的?

7 个答案:

答案 0 :(得分:15)

  • “不能被子类化”不是限制。这是一个很大的优势:它确保始终只有enum中定义的一组值,而不再是!
  • enum正确处理序列化。你可以使用类型安全的枚举来做到这一点,但它经常被遗忘(或者根本不知道)。这可确保e1.equals(e2) 始终暗示e1 == e2任意两个enume1e2(反之亦然)更重要的是)。
  • 有处理枚举的特定轻量级数据结构:EnumSetEnumMap(从this answer被盗)

答案 1 :(得分:8)

当然,其他人会在这里提到很多优点作为答案。最重要的是,您可以非常快速地撰写enums并执行许多操作,例如实施SerializableComparableequals()toString(),{{1}等等,你没有包含在你的枚举中。

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

enum

枚举时无法实现这一点:

// 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 ...
    }
}

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:3)

  

现在在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)。它们可以在类型安全开关语句中使用,并且是单例。

答案 4 :(得分:2)

EnumSetEnumMap是围绕枚举的特定功能构建的自定义数据结构。它们具有方便的额外功能,而且非常快。没有枚举就没有相应的(至少没有相同的使用优雅,见评论)。

答案 5 :(得分:2)

另外:

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

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

答案 6 :(得分:2)

单独使用句法糖是值得的:-P毕竟,这也是(:)的原因。

但严重的是,开箱即用自动命名()和序号()​​以枚举它们,在switch()中使用它们,为它们附加额外的值这一事实对他们来说是很好的理由:它避免了很多样板代码。

使用整数的传统懒惰替代方案不是类型安全的,而且更加有限。 枚举这个替代方案的一个缺点是它们不再轻量级。