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... aArgs){
    log("Exercising enumerations...");
    exerEnumsMiscellaneous();
    exerMutableEnum();
    exerEnumRange();
    exerBitFlags();
    exerEnumToStringAndValueOf();
    log("Done.");
  }

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

  /** 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 aCharge, double aMass){
      //cannot call super ctor here
      //calls to "this" ctors allowed
      fCharge = aCharge;
      fMass = aMass;
    }
    final int getCharge() {
      return fCharge;
    }
    final double getMass() {
      return fMass;
    }
    private final int fCharge;
    private final double fMass;
  }

  /**
  * 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 aCalories){
      //changes the state of the enum 'constant'
      fCalories = aCalories;
    }
    int getCalories(){
      return fCalories;
    }
    private Flavor(int aCalories){
      fCalories = aCalories;
    }
    private int fCalories;
  }
  
  /*
  * 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... aArgs){
    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> aThisSet, Set<Lang> aThatSet){
    Set<Lang> result = new LinkedHashSet<>();
    for(Lang lang: aThisSet){
      if( aThatSet.contains(lang) ) {
        result.add(lang);
      }
    }
    return result;
  }
  
  private static void log(Object aMessage){
    System.out.println(String.valueOf(aMessage));
  }
}  

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

  // PRIVATE 
  private final String fName;

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


Example 3

Suit adds these features:



import java.util.*;

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

  /**
  * 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 fOrdinal - ( (Suit)that ).fOrdinal;
  }

  private final String fName;
  private static int fNextOrdinal = 0;
  private final int fOrdinal = fNextOrdinal++;

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

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


Example 4

The AccountType enumeration:



/**
* 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.
*/
import java.io.*;
import java.util.*;

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

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

  public int compareTo(Object aObject) {
    return fOrdinal - ((AccountType)aObject).fOrdinal;
  }

  // PRIVATE
  private transient final String fName;
  private static int fNextOrdinal = 1;
  private final int fOrdinal = fNextOrdinal++;

  private AccountType (String aName) {
    fName = aName;
  }

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

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



See Also :
Use enums to restrict arguments
Modernize old code
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -