Enums
Goals
- Understand and use Java enum types.
Concepts
- enum
- ordinal
- singleton
Language
enum
Library
java.lang.Enum<E extends Enum<E>>
java.lang.Enum.name()
java.lang.Enum.ordinal()
java.lang.Enum.valueOf(Class<T> enumType, String name)
java.util.EnumMap<K extends Enum<K>, V>
java.util.EnumSet<E extends Enum<E>>
java.util.EnumSet.allOf(Class<E> elementType)
java.util.EnumSet.copyOf(Collection<E> collection)
java.util.EnumSet.noneOf(Class<E> elementType)
java.util.EnumSet.of(E element)
Preview
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.
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.
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.
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.
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.
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.
You can add other helpful methods as well:
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.
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)
.
Review
Gotchas
- Enum references can accidentally be
null
, just like references to any other type. - Make your enums reasonable, collecting a set of related values for some meaningful concept. As an example how not to create enums, see What Is Truth? (The Daily WTF).
In the Real World
- Always provide a
default:
section in yourswitch(…)
statements that use enums, throwing anAssertionError
to indicate that you had thought you covered all possible values.
Think About It
- When determining whether to add a special value to your enum or to allow
null
, think about whether the value indicates a valid enum state (e.g. a neutral gear); or the lack of any state (such as a missing engine), which would be represented bynull
.
Self Evaluation
- Can an enum reference variable be
null
? - How is the enum instance method
ordinal()
related to the enum class methodvalues()
? - Must you use the special enum collection types, or can you use any collection type to hold enum values? What difference does it make?
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.
- Do non-publications even need to indicate a publication frequency?
- Is there some way books, for example, could indicate a publication frequency?
- If non-publications are given a publication frequency, should they return
null
or some special enum value such asOTHER
if the other publication frequency values do not fit?
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
JokerLabel
example enum type uses labels appearing in the songThe Joker
by Steve Miller Band.- Some symbols are from Font Awesome by Dave Gandy.