Type Safe Enumerations

Enumerations are sets of closely related items, for example: Type-safe enumerations (also called "enum types", or simply "enums") were added to the Java language in JDK 1.5, and represent a special kind of class. If JDK 1.5 is not available, type-safe enumerations can still be implemented as a regular Java class.

Type-safe enumerations should be used liberally. In particular, they are a robust alternative to the simple String or int constants used in many older APIs to represent sets of related items.

Reminders:

Warning:

As with any class, it's easy to provide methods in an enum type which change the state of an enum constant. Thus, the term "enum constant" is rather misleading. What is constant is the identity of the enum element, not its state. Perhaps a better term would have been "enum element" instead of "enum constant".

Constructors for an enum type should be declared as private. The compiler allows non private declares for constructors, but this seems misleading to the reader, since new can never be used with enum types.

Here are some simple examples of defining and using enums:

import java.util.EnumSet;

public final class EnumExamples {

  public static final void main(String... args){
    log("Exercising enumerations...");
    exerEnumsMiscellaneous();
    exerMutableEnum();
    exerEnumRange();
    exerBitFlags();
    exerEnumToStringAndValueOf();
    log("Done.");
  }

  // PRIVATE //
  private static void log(Object text){
    System.out.println(String.valueOf(text));
  }

  /** Example 1 - simple list of enum constants.  */
  enum Quark {
    /*
    * These are called "enum constants".
    * An enum type has no instances other than those defined by its
    * enum constants. They are implicitly "public static final".
    * Each enum constant corresponds to a call to a constructor.
    * When no args follow an enum constant, then the no-argument constructor
    * is used to create the corresponding object.
    */
    UP,
    DOWN,
    CHARM,
    STRANGE,
    BOTTOM,
    TOP
  }
  
  //does not compile, since Quark is "implicitly final":
  //private static class Quarky extends Quark {}

  /**
  * Example 2 - adding a constructor to an enum.
  *
  * If no constructor is added, then the usual default constructor
  * is created by the system, and declarations of the
  * enum constants will correspond to calling this default constructor.
  */
  public enum Lepton {
    //each constant implicity calls a constructor :
    ELECTRON(-1, 1.0E-31),
    NEUTRINO(0, 0.0);
    
    /* 
    * This constructor is private.
    * Legal to declare a non-private constructor, but not legal
    * to use such a constructor outside the enum.
    * Can never use "new" with any enum, even inside the enum 
    * class itself.
    */
    private Lepton(int charge, double mass){
      //cannot call super ctor here
      //calls to "this" ctors allowed
      this.charge = charge;
      this.mass = mass;
    }
    final int getCharge() {
      return charge;
    }
    final double getMass() {
      return mass;
    }
    private final int charge;
    private final double mass;
  }

  /**
  * Example 3 - adding methods to an enum.
  *
  * Here, "static" may be left out, since enum types which are class
  * members are implicitly static.
  */
  static enum Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST; //note semicolon needed only when extending behavior
    
    //overrides and additions go here, below the enum constants

    @Override public String toString(){
      /*
      * Either name() or super.toString() may be called here.
      * name() is final, and always returns the exact name as specified in
      * declaration; toString() is not final, and is intended for presentation
      * to the user. It seems best to call name() here.
      */
      return "Direction: " + name();
    }
    /** An added method.  */
    public boolean isCold() {
      //only NORTH is 'cold'
      return  this == NORTH;
    }
  }

  /**
  * Example 4 - adding a method which changes the state of enum constants.
  */
  private enum Flavor {
    CHOCOLATE(100),
    VANILLA(120),
    STRAWBERRY(80);
    
    void setCalories(int calories){
      //changes the state of the enum 'constant'
      this.calories = calories;
    }
    int getCalories(){
      return calories;
    }
    private Flavor(int aCalories){
      calories = aCalories;
    }
    private int calories;
  }
  
  /*
  * What follows are various methods which exercise the above enums. 
  */

  private static void exerEnumsMiscellaneous(){
    //toString method by default uses the identifier
    log("toString: " + Quark.BOTTOM);

    //equals and == amount to the same thing
    if ( Quark.UP == Quark.UP ) {
      log("UP == UP");
    }

    if ( Quark.UP.equals(Quark.UP) ) {
      log("UP.equals(UP)");
    }

    //compareTo order is defined by order of appearance in the definition of
    //the enum
    if ( Quark.UP.compareTo(Quark.DOWN) < 0 ) {
      //this branch is chosen
      log("UP before DOWN");
    }
    else if ( Quark.UP.compareTo(Quark.DOWN) > 0 ) {
      log("DOWN before UP");
    }
    else {
      log("UP same as DOWN");
    }

    //values() returns Quark[], not List<Quark>
    log("Quark values : " + Quark.values());
    //the order of values matches the order of appearance :
    for ( Quark quark : Quark.values() ){
      log("Item in Quark.values() : " + quark);
    }

    log("toString : " + Direction.NORTH);
    if ( Direction.EAST.isCold() ){
      log("East is cold");
    }
    else {
      log("East is not cold.");
    }

    log("Electron charge : " + Lepton.ELECTRON.getCharge());

    //parsing text into an enum constant :
    Lepton lepton = Enum.valueOf(Lepton.class, "ELECTRON");
    log("Lepton mass : " + lepton.getMass());

    //throws IllegalArgumentException if text is not known to enum type :
    try {
      Lepton anotherLepton = Enum.valueOf(Lepton.class, "Proton");
    }
    catch (IllegalArgumentException ex){
      log("Proton is not a Lepton.");
    }

    //More compact style for parsing text:
    Lepton thirdLepton = Lepton.valueOf("NEUTRINO");
    log("Neutrino charge : " + thirdLepton.getCharge() );
  }
  
  private static void exerMutableEnum(){
    Flavor.VANILLA.setCalories(75); //change the state of the enum "constant"
    log("Calories in Vanilla: " + Flavor.VANILLA.getCalories());
  }
  
  private static void exerEnumRange(){
    for (Direction direction : EnumSet.range(Direction.NORTH, Direction.SOUTH)){
      log("NORTH-SOUTH: " + direction);
    }
  }

  private static void exerBitFlags(){
    EnumSet<Direction> directions = EnumSet.of(Direction.EAST, Direction.NORTH);
    for(Direction direction : directions) {
      log(direction);
    }
  }

  /**
  * The valueOf method uses name(), not toString(). There is no need
  * to synchronize valueOf with toString.
  */
  private static void exerEnumToStringAndValueOf(){
    Direction dir = Direction.valueOf("EAST"); //successful
    log("Direction toString : " + dir);
    dir = Direction.valueOf("Direction: EAST"); //fails
  }
}
 
Here's a simple example of using EnumSet:
import java.util.*;

public class CommonLanguage {
  
  enum Lang {ENGLISH, FRENCH, URDU, JAPANESE}

  /** Find the languages in common between two people. */
  public static void main(String... args){
    EnumSet<Lang> ariane = EnumSet.of(Lang.FRENCH, Lang.ENGLISH);
    EnumSet<Lang> noriaki = EnumSet.of(Lang.JAPANESE, Lang.ENGLISH);
    log( "Languages in common: " + commonLangsFor(ariane, noriaki) );
  }
  
  private static Set<Lang> commonLangsFor(Set<Lang> a, Set<Lang> b){
    Set<Lang> result = new LinkedHashSet<>();
    for(Lang lang: a){
      if( b.contains(lang) ) {
        result.add(lang);
      }
    }
    return result;
  }
  
  private static void log(Object message){
    System.out.println(String.valueOf(message));
  }
}  

Creating type-safe enumerations in older versions of Java

(This discussion closely follows the techniques described in the first edition of Effective Java.)

Prior to JDK 1.5, type-safe enumerations can be implemented as a regular Java class. They come in various styles, corresponding to the features they include:

Exporting a List of VALUES is highly recommended. It gives users an Iterator for cycling through all possible values. The alternative (hard-coding particular values of the enumeration) will break when those values change or are extended to include new values.

Comparison of two objects belonging to an enumeration is the same in all styles :

Example 1

VerySimpleSuit is a minimal type-safe enumeration. It is almost as simple as defining a set of related Strings or ints, apart from the private, empty constructor.

public final class VerySimpleSuit {

  /**
  * Enumeration elements are constructed once upon class loading.
  */
  public static final VerySimpleSuit CLUBS = new VerySimpleSuit();
  public static final VerySimpleSuit DIAMONDS = new VerySimpleSuit();
  public static final VerySimpleSuit HEARTS = new VerySimpleSuit();
  public static final VerySimpleSuit SPADES = new VerySimpleSuit();

  /**
  * Private constructor prevents construction outside of this class.
  */
  private VerySimpleSuit() {
    //empty
  }
}
 

Example 2

SimpleSuit is another simple style of implementing a type-safe enumeration, which includes an implementation of toString:

public final class SimpleSuit {

  /**
  * Enumeration elements are constructed once upon class loading.
  */
  public static final SimpleSuit CLUBS = new SimpleSuit ("Clubs");
  public static final SimpleSuit DIAMONDS = new SimpleSuit ("Diamonds");
  public static final SimpleSuit HEARTS = new SimpleSuit ("Hearts");
  public static final SimpleSuit SPADES = new SimpleSuit ("Spades");

  public String toString() {
    return name;
  }

  // PRIVATE 
  private final String name;

  /**
  * Private constructor prevents construction outside of this class.
  */
  private SimpleSuit(String name) {
    this.name = name;
  }
}
 

Example 3

Suit adds these features:


import java.util.*;

/** Java 1.4 (old) */
public final class Suit implements Comparable {

  /**
  * Enumeration elements are constructed once upon class loading.
  * Order of appearance here determines the order of compareTo.
  */
  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");

  public String toString() {
    return name;
  }

  /**
  * Parse text into an element of this enumeration.
  *
  * @param aText takes one of the values 'Clubs',
  * 'Diamonds', 'Hearts', 'Spades'.
  */
  public static Suit valueOf(String aText){
    Iterator iter = VALUES.iterator();
    while (iter.hasNext()) {
      Suit suit = (Suit)iter.next();
      if ( aText.equals(suit.toString()) ){
        return suit;
      }
    }
    //this method is unusual in that IllegalArgumentException is
    //possibly thrown not at its beginning, but at its end.
    throw new IllegalArgumentException(
      "Cannot parse into an element of Suit : '" + aText + "'"
    );
  }

  public int compareTo(Object that) {
    return ordinal - ( (Suit)that ).ordinal;
  }

  private final String name;
  private static int nextOrdinal = 0;
  private final int ordinal = nextOrdinal++;

  /**
  * Private constructor prevents construction outside of this class.
  */
  private Suit(String name) {
    this.name = name;
  }

  /**
  * These two lines are all that's necessary to export a List of VALUES.
  */
  private static final Suit[] values = {CLUBS, DIAMONDS, HEARTS, SPADES};
  //VALUES needs to be located here, otherwise illegal forward reference
  public static final List VALUES = Collections.unmodifiableList(Arrays.asList(values));
}
 

Example 4

The AccountType enumeration:


import java.io.*;
import java.util.*;

/**
* The only element which is serialized is an ordinal identifier. Thus,
* any enumeration values added in the future must be constructed AFTER the
* already existing objects, otherwise serialization will be broken.
* 
* Java 1.4 (old)
*/
public class AccountType implements Serializable, Comparable {

  public static final AccountType CASH =  new AccountType("Cash");
  public static final AccountType MARGIN = new AccountType("Margin");
  public static final AccountType RSP =  new AccountType("RSP");
  //FUTURE VALUES MUST BE CONSTRUCTED HERE, AFTER ALL THE OTHERS

  public String toString() {
    return name;
  }

  /**
  * Parse text into an element of this enumeration.
  *
  * @param takes one of the values 'Cash', 'Margin', 'RSP'.
  */
  public static AccountType valueOf(String text){
    Iterator iter = VALUES.iterator();
    while (iter.hasNext()){
      AccountType account = (AccountType)iter.next();
      if ( text.equals(account.toString()) ){
        return account;
      }
    }
    throw new IllegalArgumentException(
      "Cannot be parsed into an enum element : '" + text + "'"
    );
  }

  public int compareTo(Object thing) {
    return ordinal - ((AccountType)thing).ordinal;
  }

  // PRIVATE
  private transient final String name;
  private static int nextOrdinal = 1;
  private final int ordinal = nextOrdinal++;

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

  //export VALUES with these two items
  private static final AccountType[] values = {CASH, MARGIN, RSP};
  public static final List VALUES = Collections.unmodifiableList(Arrays.asList(values));

  //Implement Serializable with these two items
  private Object readResolve() throws ObjectStreamException {
    return values[ordinal];
  }
  private static final long serialVersionUID = 64616131365L;
} 

See Also :
Use enums to restrict arguments
Modernize old code