In this article we will look closely at Java Enum and understand what they are, how to define them, and how to make the most of them.
1. Overview of Java Enum
Enumeration, enum for short, was first introduced in Java JDK 5 (1.5) and it has been widely adopted by developers and it is present in virtually every single Java program and throughout the Java API library ever since.
2. What are Enums in Java?
Enums are classes and their identifiers are called self-typed constants. Every enum
in Java is implicitly public
, static
, and final
or sealed
, that is, an enum
is implicitly final
if its constants have no body and implicitly sealed
if at least one of its constants has a body.
In its simplest form, an enum
is just a list of constants. However, underneath its simplicity, there is a powerful and flexible mechanism that allows an enum to expand its capability and be nearly as robust as classes.
3. How to Declare Enum in Java
We use the enum
keyword, all in small caps, to declare an enum in Java. Do NOT mix the enum
keyword up with the abstract class Enum
, capital E.
Whenever we declare an enum
, it automatically extends
the java.lang.Enum abstract class and, since Java does NOT allow for multiple inheritance, enums cannot extend any other class nor is it possible to directly extend java.lang.Enum in your code.
Let’s declare our first enum
. We can declare it in three distinctive ways:
- In the same class file outside the class which will result in a package-private
enum
. - Nested in the class which can be either
public
,protected
, orprivate
. Adding no modifier will set the enum topublic
. Nested enums work like nested static classes. - In a separate file which can be either
public
, set explicitly, orpackage-private
adding no access modifier.
public enum OrderStatus { PROCESSED, PENDING, AUTHORIZED, SHIPPED, }
You should name enums the same way you name classes, and its constants should be all uppercased and separated by underscores should it have more than one word.
The names of variables declared class constants and of ANSI constants should be all uppercase with words separated by underscores (“_”)
Oracle 9 – Naming Conventions
3.1 Enum implicit default Constructor
Even if we do NOT add our own ordinal sequence or numbering system to our enum
constants, Java will do it internally by default. Check out the constructor in the Enum
abstract class, it takes two parameters: a String
, and an int
.
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
Let’s add two print methods, one to print an enum and another to print an Array of enums, so we can see which integer was assigned to each of the enums.
public static void printEnum(Enum<?> e) { System.out.println("Enum Name: " + e.name() + ", enum ordinal: " + e.ordinal()); } public static void printEnum(Enum<?>[] enums) { for (var e : enums) { printEnum(e); } }
As you can see, the Enum class provides two final methods (cannot be overridden) for retrieving the enum name()
, which is the enum identifier, and ordinal()
, which returns the constant position starting at 0 (declaration order).
Let’s create a couple of variables and assign our enum
to them. Keep in mind enums are static, so we cannot use the new
keyword to instantiate them. Use the .
(dot) operator to access the enum
constants.
public static void main(String[] args) { OrderStatus orderStatus = OrderStatus.AUTHORIZED; // Assigned AUTHORIZED to orderStatus variable. OrderStatus[] statuses = OrderStatus.values(); printEnum(orderStatus); System.out.println(); printEnum(statuses); }
Output:
Enum Name: AUTHORIZED, enum ordinal: 2 Enum Name: PROCESSED, enum ordinal: 0 Enum Name: PENDING, enum ordinal: 1 Enum Name: AUTHORIZED, enum ordinal: 2 Enum Name: SHIPPED, enum ordinal: 3
As you can see, for every constant in our enum there is an ordinal integer associated with it.
4. How Are Enums Classes?
Enums are useful for all the work they do for us. Let’s create a class-like enum
to demonstrate we could achieve the functionality of an enum
with a lot more work and code. For example, if we were to create the enum OrderStatus
using a regular class, call it OrderStatusClass
, it would look like this:
public class OrderStatusClass { public static final OrderStatusClass PROCESSED = new OrderStatusClass(0, "PROCESSED"); public static final OrderStatusClass PENDING = new OrderStatusClass(1, "PENDING"); public static final OrderStatusClass AUTHORIZED = new OrderStatusClass(2, "AUTHORIZED"); public static final OrderStatusClass SHIPPED = new OrderStatusClass(3, "SHIPPED"); private static final OrderStatusClass[] values = getValues(); private final int ordinal; private final String name; public OrderStatusClass(int ordinal, String name) { this.ordinal = ordinal; this.name = name; } public int ordinal() { return ordinal; } public String name() { return name; } public static OrderStatusClass[] values() { return values; } @Override public String toString() { return "Enum Name: " + name + ", enum ordinal: " + ordinal; } private static OrderStatusClass[] getValues() { int count = 0; Field[] declaredFields = OrderStatusClass.class.getDeclaredFields(); List<OrderStatusClass> statuses = new ArrayList<>(); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers())) { try { OrderStatusClass o = (OrderStatusClass) field.get(OrderStatusClass.class); if (o == null) continue; statuses.add(o); count++; } catch (IllegalAccessException e) { e.printStackTrace(); } } } return statuses.toArray(new OrderStatusClass[count]); } }
As you can see, that’s a lot of work and code. Bear in mind we have only added the two getters and the values()
method, to have all methods as with an enum
we would have to have an abstract parent class, implement interfaces, their methods, and code much more.
// Prints the Order Status AUTHORIZED. OrderStatusClass orderStatusClass = OrderStatusClass.AUTHORIZED; System.out.println(orderStatusClass); System.out.println(); // Prints all constants in OrderStatusClass class. OrderStatusClass[] statusClasses = OrderStatusClass.values(); Arrays.stream(statusClasses).forEach(System.out::println);
Output:
Enum Name: AUTHORIZED, enum ordinal: 2 Enum Name: PROCESSED, enum ordinal: 0 Enum Name: PENDING, enum ordinal: 1 Enum Name: AUTHORIZED, enum ordinal: 2 Enum Name: SHIPPED, enum ordinal: 3
We have obtained the exact same result just with so much more work. Enum saves us a lot of coding time and makes our code less error-prone.
5. Java Enum Useful Methods
Besides the two aforementioned methods, which are actually getters, there are some helpful methods to help us manipulate data using enums.
5.1 The values() Method
All the constants of an enum type can be obtained by calling the implicit public static T[] values()
method of that type. Returns an Array
containing all declared constants in the enum
in the order of their declaration.
This method is not present in the Enum abstract class, it is instead added implicitly by the compiler.
for (OrderStatus status : OrderStatus.values()) { printEnum(status); }
Output:
Enum Name: PROCESSED, enum ordinal: 0 Enum Name: PENDING, enum ordinal: 1 Enum Name: AUTHORIZED, enum ordinal: 2 Enum Name: SHIPPED, enum ordinal: 3
5.2 The valueOf(String name) Method
The implicit valueOf(String name)
method accepts a String
parameter and returns an enum
if, and only if, the parameter passed matches one of the constants. It throws NullPointerException
if name
is null or IllegalArgumentException
if name
isn’t a constant.
This static
method is also not present in the Enum
class.
OrderStatus status = OrderStatus.valueOf("PENDING"); // Uncomment the next line to throw an IllegalArgumentException. //status = OrderStatus.valueOf("DELIVERED"); // There isn't a DELIVERED constant defined. printEnum(status);
Output:
Enum Name: PENDING, enum ordinal: 1
5.3 The String toString() Method
It simply returns the final String name
variable value, so there’s no difference between invoking name()
or toString()
methods.
5.4 The final int compareTo(E e) Method
The Enum
abstract class implements the Comparable
interface. The Enum
implementation os this method compares the ordinal value of two enums, and returns a negative number if the ordinal value of the calling enum
is lower than e
‘s, a positive number if it is higher than e
‘s or zero if they are equal.
To help us demonstrate this method we will add another enum
.
public enum DayOfWeek { SUNDAY, // Implicitly 0 MONDAY, // Implicitly 1 TUESDAY, // Implicitly 2 WEDNESDAY, // Implicitly 3 THURSDAY, // Implicitly 4 FRIDAY, // Implicitly 5 SATURDAY; // Implicitly 6 }
We have this method that takes two DayOfWeek enums and compares them.
private static void usingCompareToMethod(DayOfWeek day, DayOfWeek otherDay) { int result = day.compareTo(otherDay); if (result > 0) { System.out.println(day + " comes after " + otherDay + "."); return; } if (result < 0) { System.out.println(day + " comes before " + otherDay + "."); return; } System.out.println(day + " is equal to " + otherDay + "."); }
Calling the method passing different parameters to test it.
usingCompareToMethod(DayOfWeek.FRIDAY, DayOfWeek.MONDAY); usingCompareToMethod(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY); usingCompareToMethod(DayOfWeek.TUESDAY, DayOfWeek.THURSDAY); usingCompareToMethod(DayOfWeek.WEDNESDAY, DayOfWeek.WEDNESDAY);
Output:
FRIDAY comes after MONDAY. SUNDAY comes before SATURDAY. TUESDAY comes before THURSDAY. WEDNESDAY is equal to WEDNESDAY.
5.5 Other Methods
Since enum also extends the Object class, methods such as: boolean equals(Object obj)
, int hashCode( )
and others are to be found as well. Except for Object clone()
method which just throws a CloneNotSupportedException
to prevent enums from being cloned. It is also protected
.
6. Java Enum Custom Constructor
In Java, enums can have constructors. That makes them much more flexible and powerful. Remember that every enum
has a default constructor, the one in the Enum abstract parent class. If you add one, both will be called, consequently, for every constant in your enum, both constructors are called to construct the constants. Let’s add a constructor to demonstrate it, just keep in mind all enum
constructors are private and you can never invoke them directly.
In an enum declaration, a constructor declaration with no access modifiers is
private
.In an enum declaration with no constructor declarations, a default constructor is implicitly declared. The default constructor is
Oracle Documentation – 8.9.2. Enum Body Declarationsprivate
, has no formal parameters, and has nothrows
clause.
public enum OrderStatus { PROCESSED(1, "Processed"), PENDING(2, "Pending"), AUTHORIZED(3, "Authorized"), SHIPPED(4, "Shipped"); private final String description; private final int code; OrderStatus(int code, String description) { this.code = code; this.description = description; System.out.println(this + " was constructed!"); // Print the enum being constructed } public final int getCode() { return code; } public final String getDescription() { return description; } }
Now, just by calling Enum.values()
, which will construct an array of enum
constants, we should get their names printed out on the console. Keep in mind this process occurs just once (to construct the static members) as soon as we request any data from the enum
.
OrderStatus[] statuses = OrderStatus.values(); OrderStatus[] orders = OrderStatus.values();
Output:
PROCESSED was constructed! PENDING was constructed! AUTHORIZED was constructed! SHIPPED was constructed!
As demonstrated by the result above, constants are only constructed once, regardless of how many times you fetch them.
7. Java Enum Custom Methods
It is also possible to add custom methods to an enum in Java just like you would in a class. For example, the Enum
class already provides us with a method to convert a String
to an enum and it throws an IllegalArgumentException
if not found.
Now let’s say we have a database where we save our enums as integers, which is a quite widespread practice, and we need a method that takes an int
and returns the enum
associated with it.
public static OrderStatus toEnum(int code) { for (var e : OrderStatus.values()) { if (e.code == code) return e; } throw new IllegalArgumentException("No CONSTANT found for code: " + code); }
The previous toEnum()
method is fine if you have a couple of constants. However, let’s say you have an enum
with over a hundred constants, having O(n) doesn’t give you the absolute best performance when you can achieve O(1). So, let’s add the toEnumFaster(int code)
method.
public static OrderStatus toEnumFaster(int code) { if (code > 0 && code <= OrderStatus.values().length) return OrderStatus.values()[code-1]; throw new IllegalArgumentException("No CONSTANT found for code: " + code); }
Bear in mind it is only possible because there is a relation between the code and the ordinal
number, and ours is code
minus one. But it can only be achieved if you use enum ordinal
directly or if it is possible to establish a pattern between it and your own numbering system.
Keep in mind at this point, our enum code ranges from one to five and any code outside this range will result in an IllegalArgumentException
.
OrderStatus orderStatus = OrderStatus.toEnumFaster(4); System.out.println(orderStatus); // Will throw an IllegalArgumentException. orderStatus = OrderStatus.toEnum(0);
Notice that this time we didn’t use our printEnum method and the reason is that it has been designed to print the enum name
and ordinal
fields, but right now our enum has custom fields: description
and code
, so, to print using the new fields, we added a toString()
method to our enum
.
@Override public String toString() { return "Enum description: " + description + ", code: " + code + "."; }
Remember that while the code provided as parameter is four, this Enum’s ordinal is three.
OrderStatus orderStatus = OrderStatus.toEnumFaster(4); System.out.println(orderStatus); // Will throw an IllegalArgumentException. orderStatus = OrderStatus.toEnum(0);
Output:
Enum description: Authorized, code: 4. Exception in thread "main" java.lang.IllegalArgumentException: No CONSTANT found for code: 0
8. Java Enum Constant Specific Body
Enum bodies are implemented as anonymous classes, and it allows developers to override the behavior of any constant or all of them enabling constants to have specific functionality that differs from the one defined in the enum
.
Let’s add a get status method to our enum
.
public String getStatus() { return "Don't worry, your Order is being taken care of. :)"; }
Now, let’s add one more constant to our enum
and override the method we have just created.
CANCELED(6, "Canceled") { @Override public String getStatus() { return "We can no longer take care of your Order. It has been canceled. :("; } };
Now let’s call getStatus() for every constant in our enum
.
for (var e : OrderStatus.values()) { System.out.println("Status for " + e.getDescription() + " is " + e.getStatus()); }
Output:
Status for Processed is Don't worry, your Order is being taken care of. :) Status for Pending is Don't worry, your Order is being taken care of. :) Status for Authorized is Don't worry, your Order is being taken care of. :) Status for Shipped is Don't worry, your Order is being taken care of. :) Status for Canceled is We can no longer take care of your Order. It has been canceled. :(
8.1 Enum Constants Class Bodies with Abstract Method
It’s possible to have abstract methods in an enum
which you must implement for every constant, so let’s add another enum
called Operation.
// Source: https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.3 // Code has been adapted to serve this article's purpose. public enum Operation { PLUS { public double eval(double x, double y) { return x + y; } }, MINUS { public double eval(double x, double y) { return x - y; } }, TIMES { public double eval(double x, double y) { return x * y; } }, DIVIDED_BY { public double eval(double x, double y) { return x / y; } }; // Each constant supports an arithmetic operation public abstract double eval(double x, double y); }
Now let’s see how we can perform all those operations using enum
constants.
private static void usingConstantSpecificBodyWithAbstractMethod(double x, double y) { for (var op : Operation.values()) System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y)); }
Parameters passed for x and y: 10 and 10. Output:
10.0 PLUS 10.0 = 20.0 10.0 MINUS 10.0 = 0.0 10.0 TIMES 10.0 = 100.0 10.0 DIVIDED_BY 10.0 = 1.0
9. How to Emulate Extensible Enums with Interfaces
Before JDK 1.5 it was common to use what was called the Enum Pattern (NOT covered here), which was not type-safe, and when Java first introduced enum
, it was better than the Enum Pattern in every single aspect, except it isn’t possible to use inheritance as discussed in Chapter 3. So, to bypass this limitation, we can make use of extensible enums. However, one drawback of this approach is that you must implement the interface in every enum, which you wouldn’t if it were an abstract class, for instance.
9.1 Defining a Common Interface
First, let’s create an interface and add common methods that we need every single enum in our system to implement because we don’t want our application enums to use ordinal
and name
, we want them to use code
and description
instead.
public interface Enumerable { int getCode(); String getDescription(); // Require Java 1.8 or above. default String format() { return "Enum Name: " + this.getDescription() + ", enum code: " + this.getCode() + "."; } default String getInitials() { return this.getDescription().substring(0, 3); } // Require Java 1.8 or above. static <T extends Enumerable> T toEnum(final T[] values, int code) { if (values == null) throw new NullPointerException(); if (code > 0 && code <= values.length) for (var e : values) if (e.getCode() == code) return e; throw new IllegalArgumentException("No CONSTANT found for code: " + code); } }
From Java 8 (JDK 1.8) on, it is possible to implement methods in an interface, it requires the default
keyword though, and every enum
that implements this interface will have those methods. In addition, we can also have static
methods in an interface that does not require an object or implementation.
For the toEnum method we have two options: we either implement it on the interface which requires us to pass the array as a parameter (this is the drawback we talked about earlier in this article), or we implement it in every enum, which will eliminate the need to pass an array as a parameter, but it will require us to rewrite this method for every enum
our application has.
9.1.1 Using Instance Fields instead of Ordinals
You may be asking why we have created two methods we already have e.g. (name()
& ordinal()
)? Besides being bug-prone, using Java’s built-in ordinal may create a maintenance nightmare. Besides, it is designed to be used with other derived data structures and methods such as public final int compareTo(E o)
.
Most programmers will have no use for this field. It is designed for use by sophisticated enum-based data structures, such as
Oracle Java Documentationjava.util.EnumSet
andjava.util.EnumMap
.
Never derive a value associated with an enum from its ordinal; store it in an instance field instead.
Effective Java by Joshua Block
Take our OrderStatus
as an example, if we were to add one more status, OVERDUE for instance, into our enum, and let’s say, for performance and memory management purposes, we only save the ordinal
associated with it into the Database. Unless we add it at the end of our enum
constants, we may end up with a bug that is hard to identify and mess up with the entire application.
public enum OrderStatus { PROCESSED, // Before 0, Now 0 - OK PENDING, // Before 1, Now 1 - OK OVERDUE, // Before ? (AUTHORIZED was 2), Now 2 - NOT OK AUTHORIZED, // Before 2, Now 3 - NOT OK SHIPPED, // Before 3, Now 4 - NOT OK CANCELED; // Before 4, Now 5 - NOT OK }
As you can see, the alteration above would lead to undesirable behavior in an application.
9.2 Implementing the Interface.
Let’s implement our recent-created interface on both enums: OrderStatus and DayOfWeek, and since the OrderStatus enum already has those methods, we won’t be prompted to implement them. However, our DayOfWeek enum
still doesn’t.
public enum DayOfWeek implements Enumerable { SUNDAY(1, "Sunday"), // ordinal 0 -> code 1 MONDAY(2, "Monday"), // ordinal 1 -> code 2 TUESDAY(3, "Tuesday"), // ordinal 2 -> code 3 WEDNESDAY(4, "Wednesday"), // ordinal 3 -> code 4 THURSDAY(5, "Thursday"), // ordinal 4 -> code 5 FRIDAY(6, "Friday"), // ordinal 5 -> code 6 SATURDAY(7, "Saturday"); // ordinal 6 -> code 7 private final String description; private final int code; DayOfWeek(int code, String description) { this.code = code; this.description = description; } @Override public int getCode() { return code; } @Override public String getDescription() { return description; } @Override public String toString() { return format(); } }
Let’s revisit those printEnum
methods we have added to our application and update them to accept objects of type Enumerable.
public static void printEnum(Enumerable e) { System.out.println(e); } public static void printEnum(Enumerable[] enums) { for (var e : enums) printEnum(e); }
Now we should be able to get any Enum by passing a valid enum
code or to get the initials of an enum.
printEnum(Enumerable.toEnum(DayOfWeek.values(), 7)); printEnum(Enumerable.toEnum(DayOfWeek.values(), 4)); printEnum(Enumerable.toEnum(OrderStatus.values(), 1)); printEnum(Enumerable.toEnum(OrderStatus.values(), 6)); System.out.println(); for (var e : DayOfWeek.values()) System.out.println(e.getInitials() + " -> " + e.getDescription()); // Will throw an IllegalArgumentException. printEnum(Enumerable.toEnum(DayOfWeek.values(), 0));
Output:
Enum Name: Saturday, enum code: 7. Enum Name: Wednesday, enum code: 4. Enum Name: Processed, enum code: 1. Enum Name: Canceled, enum code: 6. Sun -> Sunday Mon -> Monday Tue -> Tuesday Wed -> Wednesday Thu -> Thursday Fri -> Friday Sat -> Saturday Exception in thread "main" java.lang.IllegalArgumentException: No CONSTANT found for code: 0 at com.youlearncode.enums.Enumerable.toEnum(Enumerable.java:26) at com.youlearncode.program.Main.usingCommonInterface(Main.java:42) at com.youlearncode.program.Main.main(Main.java:28)
9.3 Implementing toEnum Using Reflection
A third way out would be to use reflection and, instead of passing an array, we’d pass the class type.
static <T extends Enum<T>> T toEnum(Class<T> enumClass, int code) { Method valuesMethod = getValuesMethod(enumClass); @SuppressWarnings("unchecked") final T[] values = (T[]) getValues(valuesMethod); if (values == null) throw new NullPointerException(); if (code > 0 && code <= values.length) return values[code - 1]; throw new IllegalArgumentException("No CONSTANT found for code: " + code); } private static Object[] getValues(Method valuesMethod) { if (valuesMethod == null) return null; try { return (Object[]) valuesMethod.invoke(null, (Object[]) null); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return null; } private static <T extends Enum<T>> Method getValuesMethod(Class<T> enumClass) { if (enumClass == null) return null; try { return enumClass.getDeclaredMethod("values"); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; }
As you can see, it is a lot more coding and exceptions to catch.
printEnum(Enumerable.toEnum(DayOfWeek.class, 7)); printEnum(Enumerable.toEnum(DayOfWeek.class, 1)); printEnum(Enumerable.toEnum(OrderStatus.class, 1)); printEnum(Enumerable.toEnum(OrderStatus.class, 5));
Output:
Enum Name: Saturday, enum code: 7. Enum Name: Sunday, enum code: 1. Enum Name: Processed, enum code: 1. Enum Name: Shipped, enum code: 5.
We got the very same results using this method as we did by using the previous one.
10. Conclusion
By now you should have a great understanding of how enum
works in Java, its functionalities and limitations, and how to use them effectively in Java. Feel free to check the source code on our GitHub Page.