Enums

Goals

Concepts

Language

Library

Preview

Enum classes for an airline rewards account.
public enum RewardsLevel {SILVER, GOLD, PLATINUM}
public enum SeatPreference {AISLE, WINDOW}
public class AirlineAccount {

  private final RewardsLevel rewardsLevel;

  private final SeatPreference seatPreference;

  public classAirlineAccount(@Nonnull final RewardsLevel rewardsLevel, @Nonnull final SeatPreference seatPreference) {
    this.rewardsLevel = checkNotNull(rewardsLevel);
    this.seatPreference = checkNotNull(seatPreference);
    …
  }

Lesson

Java enums are special classes that allow you to create a predefined, ordered list of values that are type-safe yet can be used efficiently in decisions. We might want to determine if a car is turned on or not, as in the examples in the lessons on inner classes. In our first conceptualization we only had two states—on or off—making it seemingly a perfect candidate for a boolean variable.

private boolean on = false; //whether the vehicle is turned on

But even this simple example gives us no type safety at all. What if we were to try to assign a different boolean result to the variable? Java would find no error.

this.on = isOilChangeNeeded();  //WRONG --- but compiles just fine

Because isOilChangeNeeded() is also boolean, the compiler lets us assign one to the other, even though they mean completely different things. And having a state with two values was just lucky; maybe we later decide that we want three states: off (ignition off), on (ignition on for car to run), and accessories (turning on e.g. radio but not allowing the car to run). Before the introduction of generics, Java programs had to resort to using special numbers, like this:

public static final int IGNITION_OFF = 0;
public static final int IGNITION_ACCESSORIES = 1;
public static final int IGNITION_ON = 2;

private int ignitionState;

Now we're in a bit of a worse state before. Not only could we accidentally assign any int variable to ignitionState (e.g. ignitionState = currentMileage), some of the values assigned might not even be valid ignition states (e.g. ignitionState = 34).

Declaring Enums

The Java enum (for enumeration) solves these problems and more. You usually define an enum in a top-level .java file, just like classes and interfaces. Instead of the keyword class or interface, you will use the keyword enum. You will then list, between the braces, the comma-separated identifiers that you want to be part of the enumerated values.

IgnitionState enum.
public enum IgnitionState {

  /** Ignition turned off. */
  OFF,

  /** Ignition turned to accessories position. */
  ACCESSORIES,

  /** Ignition turned on, allowing the engine to run. */
  ON;
}

Now we can use the enum type similar to any other type. The values are referenced using the enum type name prefix and the dot . separator.

Declaring an initializing an enum variable.
private IgnitionState ignitionState = IgnitionState.OFF;

When you declare the above enum, the Java compiler will generate a class called IgnitionState which will extend the Java class java.lang.Enum<E extends Enum<E>>. This class will contain constant, static variables with the same names as the enumerated values you indicated. Each of those constant values will be an instance of IgnitionState.

Enumerating Enums

You can can access all the enum values, in order, for a particular type by using the special static values() method generated for the enum class.

Retrieving all value instances of an enum type.
final IgnitionState[] ignitionStates = IgnitionState.values();  //returns [OFF, ACCESSORIES, ON]

Comparing Enums

Java provides a java.lang.Enum.name() method to all your enum instances, so you can call ignitionState.getName() if you want to know the name of the value (e.g. OFF ) of the enum. But don't use the enum name() for comparing enums; Java provides much better ways to check enum values for equality and even order.

When loading the IgnitionState class, Java will make sure that all the enum values are singletons, so that every occurrence of IgnitionState.OFF for example will be the same instance. This allows you to compare enum values just as you would primitive types:

if(newIgnitionState == IgnitionState.OFF) {
  //TODO turn off the engine
}

Java maintains the order in which you declare your enum values, assigning each of them an ordinal—an integer value, starting with 0 for the first value. If order is important to you, you can retrieve the ordinal using java.lang.Enum.ordinal().

Java also provides some conveniences under the hood that allow you to efficiently use enums in switch(…) statements. To off both features, let's make another enum named GearState which indicates which gear a motor vehicle is in.

Switching on an enum and looking up an enum value by ordinal.
public enum GearState {
  REVERSE, NEUTRAL, GEAR1, GEAR2, GEAR3, OVERDRIVE;
}
/** Changes to the next gear if driving.
 * @throws IllegalStateException if vehicle is in reverse.
 * @throws IllegalStateException if vehicle is already in overdrive.
 */
public void changeGearNext() {
  final GearState currentGear = getGearState();
  final GearState nextGear;
  switch(currentGear) {
    case REVERSE:
      throw new IllegalStateException("Cannot go to next gear when in reverse.");
    case NEUTRAL:
    case GEAR1:
    case GEAR2:
    case GEAR3:
      nextGear = GearState.values()[currentGear.ordinal() + 1];
      break;
    case OVERDRIVE:
      throw new IllegalStateException("Already in the highest gear.");
    default:
      throw new AssertionError("Unrecognized gear state "+currentGear);
  }
  setGearState(nextGear); //set the next gear we determined
}

Creating Enums

You normally immediate have access to a constant instance of each enum value in your code. But if for some reason you only have access to an enum's string representation, the one returned by Enum.name(), you can use the factory method of the generated enum type, such as GearState.valueOf("NEUTRAL"), as you would expect for value objects.

If you only have access to an enum's class, you can use the general factory method Enum.valueOf(Class<T> enumType, String name), which allows you to get access to any enum value.

Enum Members

Because enums are really classes, you can add variables, methods, and even constructors. Here's how you might add a getDescription() method to your IgnitionState enum.

Adding a member method to an enum class.
public enum IgnitionState {
  OFF, ACCESSORIES, ON;

  /** @return A description of the ignition state.*/
  public String getDescription() {
    switch(this) {
      case OFF:
        return "Turned off";
      case ACCESSORIES:
        return "Only accessories";
      case ON:
        return "Turned on";
      default:
        throw new AssertionError("Unrecognized ignition state " + this);
    }
  }
}

You could get the same result by adding a member variable, just like in a normal class! This works because the enum values are essentially creating new instances of the enum.

Adding a member variable with getter method to an enum class.
public enum IgnitionState {
  OFF("Turned off"), ACCESSORIES("Only accessories"), ON("Turned on");

  private final String description;

  /** @return A description of the ignition state.*/
  public String getDescription() {
    return description;
  }

  /** Constructor.
   * @param description The description of the ignition state.
   */
  private IgnitionState(@Nonnull final String description) {
    this.description = checkNotNull(description);
  }
}

You can add other helpful methods as well:

Adding another member method to an enum class.
public enum IgnitionState {
  OFF, ACCESSORIES, ON;

  /** @return Whether the ignition is in a state
   *      that allows the use of accessories.
   */
  public boolean canUseAccessories() {
    return this == ACCESSORIES || this == ON;
  }
}

Enum Interfaces

An enum can implement an interface, just like any other class! We could have a Described interface that provides a description, and have our enum class implement that interface.

Implementing an interface with an enum class.
public interface Described {

  /** @return A description of the ignition state.*/
  String getDescription();

}
public enum IgnitionState implements Described {
  OFF, ACCESSORIES, ON;

  @Override
  public String getDescription() {
    …
  }
}

Enum Collections

Because enum instances are objects, you can use them in Java ADT types such as List<E>, Set<E>, and Map<K, V>. However Java has provided special java.util.EnumSet<E extends Enum<E>> and java.util.EnumMap<K extends Enum<K>, V> types that take advantage of the special properties of enums. These two types work just like normal Set<E> and Map<K, V> implementations for the most part, except that their storage implementation are especially compact and efficient. The enum set type also provide special fluent static factory methods, such as EnumSet.of(E element), EnumSet.allOf(Class<E> elementType), and EnumSet.copyOf(Collection<E> collection).

Creating a set of labels representing the appellations in The Joker by Steve Miller Band.
package com.example.stevemiller;

/** @see <a href="https://en.wikipedia.org/wiki/The_Joker_%28song%29">"The Joker" song</a> */
public enum JokerLabel {
  SPACE_COWBOY,
  GANGSTER_OF_LOVE,
  MAURICE,
  PICKER,
  GRINNER,
  LOVER,
  SINNER,
  JOKER,
  SMOKER,
  MIDNIGHT_TOKER;
}
package com.example.app;

import com.example.stevemiller.JokerLabel;
import static com.example.stevemiller.JokerLabel.*;

public class Application {

  public static void main(final String[] args) {
    final Set<JokerLabel> labels = EnumSet.of(PICKER, GRINNER, LOVER, SINNER);
    …
    labels.add(JOKER);
    labels.add(SMOKER);
    labels.add(MIDNIGHT_TOKER);
    …
  }

Review

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Add an enum to your booker project periodicals type indicating the publication frequency (e.g. monthly, quarterly, yearly) of the periodical. Provide a method on your enum that indicates an integer value of how many times a year the frequency indicates.

Decide how this publication frequency applies to non-periodicals.

Discuss with your team-members the best design approach for your model. You may or may not then choose the same approach as your team members, but indicate the design decision you took in the Javadoc for your enum class.

See Also

References

Acknowledgments