Advantages of Java's enum over the old "Typesafe Enum" pattern?

In Java prior to JDK1.5, the "Typesafe Enum" pattern was the usual way to implement a type that can only take a finite number of values:

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

(see e.g. Item 21 from Bloch's Effective Java).

Now in JDK1.5+, the "official" way is obviously to use enum:

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

  private final String name;

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

Obviously, the syntax is a bit nicer and more concise (no need to explicitly define fields for the values, suitable toString() provided), but so far enum looks very much like the Typesafe Enum pattern.

Other differences I am aware of:

  • enums automatically provide a values() method
  • enums can be used in switch() (and the compiler even checks that you don't forget a value)

But this all looks like little more than syntactic sugar, with even a few limitations thrown in (e.g. enum always inherits from java.lang.Enum, and cannot be subclassed).

Are there other, more fundamental benefits that enum provides that could not be realized with the Typesafe Enum pattern?


Solution 1:

  • "cannot be subclassed" isn't a restriction. It's one of the big advantages: It ensures there there is always only ever exactly the set of values defined in the enum and no more!
  • enum correctly handles serialization. You can do that with type-safe enums as well, but it's often forgotten (or simply not known). This ensures that e1.equals(e2) always implies e1 == e2 for any two enum values e1 and e2 (and vice versa, which is probably more important).
  • There are specific lightweight data structures that handle enums: EnumSet and EnumMap (stolen from this answer)

Solution 2:

Of course there are lots of advantages other people will mention here as answers. Most importantly, you can write enums very fast and they do a lot of things like implement Serializable, Comparable, equals(), toString(), hashCode(), etc, which you didn't include in your enum.

But I can show you a serious drawback of enum (IMO). Not only can't you subclass them at will, but you can't equip them with a generic parameter. When you could write this:

// 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 is not possible with an enum:

// This can't be done
public enum DataType<T> {

    // Neither can this...
    INT<Integer>("int", Integer.class), 
    INT4<Integer>("int4", Integer.class), 

    // [...]
}

Solution 3:

Now in JDK1.5+, the "official" way is obviously to use enum:

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

  private final String name;

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

Actually, it's more like

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

because enums already provide a name() method. Additionally, they provide an ordinal() method (which enables efficient data structures like EnumSet and EnumMap), implement Serializable, override toString, provide values() and valueOf(String name). They can be used in a type safe switch statement, and are singletons.

Solution 4:

Your type-safe enum implementation is a bit oversimplified. When you deal with serialization it will become much more complicated.

Java enums solve the problem with serialization/deserialization. enum's are guarantied to be unique and you can compare them with == operator.

Read correspondent chapters in Effective Java 2nd Edition (about using enums instead of singletons, about using EnumSets etc).