Enumerations are sets of closely related items, for example:
- cardinal directions - north, south, east, west
- types of novels - mystery, classic, fantasy, romance, science-fiction
- flavours of ice cream - chocolate, vanilla, raspberry, maple
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:
- enums are implicitly final subclasses of java.lang.Enum
- if an enum is a member of a class, it's implicitly static
- new can never be used with an enum, even within the enum type itself
- name and valueOf simply use the text of the enum constants, while toString may be overridden to provide any content, if desired
- for enum constants, equals and == amount to the same thing, and can be used interchangeably
- enum constants are implicitly public static final
- the order of appearance of enum constants is called their "natural order", and defines the order used by other items as well : compareTo, iteration order of values , EnumSet, EnumSet.range.
- enums have a built-in serialization mechanism, which can't be overridden. The mechanism uses the name and valueOf methods.
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:
- a List of VALUES, for iterating over all values of the enumeration
- implementing a valueOf method for parsing text into an enumeration element
- implementing Comparable
- implementing Serializable
Comparison of two objects belonging to an enumeration is the same in all styles :
- the Object.equals method is never overridden
- either equals or == can be used to perform comparisons, since they amount to the same thing
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:
- a valueOf method for parsing text into an enumeration element
- exports a List of VALUES
- implements Comparable
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:
- exports a valueOf method for parsing text into an enumeration element
- exports a List of VALUES
- implements Comparable
- implements Serializable
/** * 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; }