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:
final
subclasses of java.lang.Enum
static
new
can never be used with an enum, even within the enum type
itselfname
and valueOf
simply use the text of the enum constants, while toString
may
be overridden to provide any content, if desiredequals
and ==
amount to the same
thing, and can be used interchangeablypublic static final
compareTo
,
iteration order of values
, EnumSet
, EnumSet.range
.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... 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:
List
of VALUES
, for iterating over all values of the
enumerationvalueOf
method for parsing text into an enumeration
elementComparable
Serializable
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 :
Object.equals
method is never overriddenequals
or ==
can be used to perform comparisons,
since they amount to the same thingVerySimpleSuit
is a minimal type-safe enumeration. It is almost
as simple as defining a set of related String
s or int
s,
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 } }
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; } }
Suit
adds these features:
valueOf
method for parsing text into an enumeration elementList
of VALUES
Comparable
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)); }
The AccountType
enumeration:
valueOf
method for parsing text into an enumeration
elementList
of VALUES
Comparable
Serializable
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; }