Home/Blog/Programming/What is an enum in Java?
Home/Blog/Programming/What is an enum in Java?

What is an enum in Java?

Jun 05, 2023 - 9 min read
Saqib Ilyas
What is Enum in Java
What is Enum in Java

Become a Software Engineer in Months, Not Years

From your first line of code, to your first day on the job — Educative has you covered. Join 2M+ developers learning in-demand programming skills.

An enum is a programming construct that is available in most programming languages. In this blog, we'll see what makes enums useful, how to use an enum in your code, and when not to use an enum, all in the context of the Java programming language. So, let's begin.

Understanding enum in Java

Enumerations, or enums for short, are a custom data type that can take on a value from among a few options that the programmer explicitly lists down. Enums are useful in scenarios where we want to represent entities with discrete states, day of the week, the state of matter (solid, liquid, or gas), etc.

An example scenario

Suppose that we are creating an application to manage the course creation process at Educative. Let's further suppose that the course creation pipeline consists of the following stages:

  • Ideation: A course idea is proposed. It may be approved or rejected.

  • Creation: Content is written.

  • Proofreading: Content is proofread.

  • Launch: The course is launched.

An example workflow that we want to model
An example workflow that we want to model

First approach

Here's a Java program that represents this flow:

class Course {
// Represents the current stage of the course creation
int courseStage;
// The proposed course name
String courseName;
// Constructor for the course
public Course(String name) {
courseName = name;
courseStage = 0;
}
// Approve a course
void approve() {
if (courseStage != 0) {
System.out.println("Course is already approved!");
return;
}
courseStage = 1;
}
// Send a course for proofreading
void sendForProofreading() {
if (courseStage != 1) {
System.out.println("Only a course in creation stage can be sent for proofreading!");
return;
}
courseStage = 2;
}
// Launch a course
void launch() {
if (courseStage != 2) {
System.out.println("Only a proofread course not already launched, can be launched!");
return;
}
courseStage = 3;
}
// Returns a string representation of the course
public String toString() {
StringBuilder courseRep = new StringBuilder();
courseRep.append("The course " + courseName + " is in ");
if (courseStage == 0) {
courseRep.append("Ideation");
}
else if (courseStage == 1) {
courseRep.append("Creation");
}
else if (courseStage == 2) {
courseRep.append("Proofreading");
}
else {
courseRep.append("Launch");
}
courseRep.append(" stage.");
return courseRep.toString();
}
}
class Application {
public static void main( String args[] ) {
Course c1 = new Course("Docker for Developers");
System.out.println(c1);
c1.approve();
System.out.println(c1);
c1.approve();
c1.sendForProofreading();
System.out.println(c1);
c1.launch();
System.out.println(c1);
}
}

We have defined a class named Course that represents a course in the pipeline. This class has an int attribute named courseStage, which represents the current stage of the course. We are using the following encoding for the course stage:

Our integer encoding for the course pipeline stages

Integer value

Corresponding pipeline stage

0

Ideation

1

Creation

2

Proofreading

3

Launch

We have defined methods in the Course class to move the course from one pipeline stage to the next. We've also implemented some rudimentary sanity checking in these methods. For example, as we can see in lines 13 – 16 of the code above, we don't approve a course unless it is in the ideation stage. In main(), we create a Course object named c1 and take it through the pipeline (lines 58 – 66).

The problem with this approach

There may be several problems with this solution depending on the perspective that you look at it from. From this blog's perspective, there are two main problems:

1- The code assigned to each stage is arbitrary and like a magic number.

2- Changing the code to adapt to changes in the course creation process is prone to error.

Imagine what happens if we decide to insert a new stage somewhere in the pipeline. Pause for a moment and think about this.

For the moment, ignore the open-closed principle and assume that we are willing to modify the source code.

Let's think about where in the pipeline we decide to insert a new stage.

Case 1: Inserting a new stage at the end of the pipeline

Suppose that we sometimes run marketing campaigns after a course is launched. In this case, we could add a stage at the end of the pipeline and update it to the following:

An example workflow that we want to model
An example workflow that we want to model

Does this create any problems in our code? We still need to add a new promotion() method to the Course class. The courseStage attribute for a course in the promotion stage would be set to 4, and we'll need to add a new condition to the toString() method. That's it. No other method implementations get affected.

Case 2: Inserting a new stage at the beginning of the pipeline

Suppose that we decide to insert a new stage, say "author search", to the pipeline.

An example workflow that we want to model
An example workflow that we want to model

In this case, we'd need to add a method authorSearch() to the Course class. What about the courseStage encoding? Do we assign -1 to represent this new stage since 0 is already taken for the Ideation stage? That doesn't feel right.

If we re-assign 0 to Author Search, and increment the code for all existing stages, then we'd have to update code in all the methods. That's not considered a good practice. It's always possible to forget to make changes at one or two places, thereby introducing bugs.

Case 3: Inserting a new stage somewhere in the middle of the pipeline

By now, the suspense is broken. This is kind of similar to case 2. So, no point in discussing this in detail. Summary: there's a problem.

Better approach: using enums

Most programming languages provide enumerations as a solution to this kind of problem. Enumerations, or enums, for short, are a custom data type that can take on a value from among a few options that the programmer explicitly lists down. Here's our earlier program re-written with enums.

enum CourseStage {
IDEATION,
CREATION,
PROOFREADING,
LAUNCH
}
class Course {
// Represents the current stage of the course creation
CourseStage courseStage;
// The proposed course name
String courseName;
// Constructor for the course
public Course(String name) {
courseName = name;
courseStage = CourseStage.IDEATION;
}
// Approve a course
void approve() {
if (courseStage != CourseStage.IDEATION) {
System.out.println("Course is already approved!");
return;
}
courseStage = CourseStage.CREATION;
}
// Send a course for proofreading
void sendForProofreading() {
if (courseStage != CourseStage.CREATION) {
System.out.println("Only a course in creation stage can be sent for proofreading!");
return;
}
courseStage = CourseStage.PROOFREADING;
}
// Launch a course
void launch() {
if (courseStage != CourseStage.PROOFREADING) {
System.out.println("Only a proofread course not already launched, can be launched!");
return;
}
courseStage = CourseStage.LAUNCH;
}
// Returns a string representation of the course
public String toString() {
StringBuilder courseRep = new StringBuilder();
courseRep.append("The course " + courseName + " is in ");
if (courseStage == CourseStage.IDEATION) {
courseRep.append("Ideation");
}
else if (courseStage == CourseStage.CREATION) {
courseRep.append("Creation");
}
else if (courseStage == CourseStage.PROOFREADING) {
courseRep.append("Proofreading");
}
else {
courseRep.append("Launch");
}
courseRep.append(" stage.");
return courseRep.toString();
}
}
class Application {
public static void main( String args[] ) {
Course c1 = new Course("Docker for Developers");
System.out.println(c1);
c1.approve();
System.out.println(c1);
c1.approve();
c1.sendForProofreading();
System.out.println(c1);
c1.launch();
System.out.println(c1);
}
}

We declare an enumeration on lines 1 – 6 using the enum keyword, which is followed by a name for the enum. Within the curly braces, we list down constants that represent all the possible values for this enumeration. It is a convention to use all caps for the enum constants.

An enum declaration is like a class declaration. Just as an object of a (non-static) class needs to be defined somewhere in the program, an enum variable must also be declared. We do this in line 9 in the code above. From this point onwards, a variable of type CourseStage can take on any one of the values defined on lines 2 – 5. On line 15, we initialize this variable to CourseStage.IDEATION. Notice that we use the enum name followed by a dot operator followed by the constant value. Similarly, we've used the enum values throughout the rest of the program.

This gets rid of the magic numbers from the program. Updating the code to adapt to changes in the course creation process is still prone to error. However, we no longer have the magic numbers splattered all over the code. The constants are human-readable and they convey the meaning to the reader.

Let's see what happens if we try to display an enum value:

enum CourseStage {
IDEATION,
CREATION,
PROOFREADING,
LAUNCH
}
class Application {
public static void main( String args[] ) {
System.out.println(CourseStage.IDEATION);
System.out.println(CourseStage.CREATION);
System.out.println(CourseStage.PROOFREADING);
System.out.println(CourseStage.LAUNCH);
}
}

The above program displays text like "IDEATION," "CREATION," etc on the console. If you are coming from a C++ background, you might have expected to see integers 0, 1, 2, and 3 on the console.

Let's see if we can force the Java compiler to use the enum constants as integers when used in arithmetic.

enum CourseStage {
IDEATION,
CREATION,
PROOFREADING,
LAUNCH
}
class Application {
public static void main( String args[] ) {
System.out.println(CourseStage.IDEATION + 1);
}
}

Nope! It turns out that this throws a compilation error. But, don't worry! The base class for all Java enums is java.lang.Enum. This base class provides a few useful methods, one of which is ordinal(), which represents the ordinal corresponding to a particular enum constant. So, we could fix the compilation error in the above program as follows:

enum CourseStage {
IDEATION,
CREATION,
PROOFREADING,
LAUNCH
}
class Application {
public static void main( String args[] ) {
System.out.println(CourseStage.IDEATION.ordinal() + 1);
}
}

When is using enums considered a bad idea?

Several practitioners discourage the use of enums and call it a code smell. A primary reason for this is that using enums often results in switch-case or if-else ladders splattered all over the program. This violates the single-responsibility principle. If we have to make any changes to an enum declaration, we have to update the code at several places. Missing the changes at any place in the program may introduce bugs.

Instead of enums, practitioners recommend using classes with inheritance. In the above example, we could create a hierarchy with a CourseState class at the base, and derived classes Ideation, Creation, ProofReading, Launch etc. above it.

Other features of enums in Java

Enums in Java are very similar to classes because in addition to the constants representing the discrete values for the type, we can declare other member variables and methods too.

Let's take an example in which we have implemented a distributed leader election protocol. In this protocol, when a node comes online, it sends a HELLO packet to discover the network. In response, it receives one or more HELLO packets as acknowledgment. Then, it sends a JOIN packet to join the group of nodes already there. The nodes elect a leader by sending ELECTLEADER packets. A node may leave any time by sending a LEAVE message. Each type of packet has a predefined size.

Nodes in a distributed system that will elect a leader
Nodes in a distributed system that will elect a leader

In the following program, we define an enum to represent these different types of packets. We have represented the packet types using names like HELLO and JOIN. But what is that parenthesis syntax on line 2, for instance? It looks like a constructor, right? In fact, it is. You'll find that thix constructor is defined on lines 7 -- 9. It takes an argument sz and assigns its value to the enum instance member variable size (defined on line 6). So, line 2 effectively means that if we declare a variable of type PacketType.HELLO, it will have an instance member variable size equal to 10.

We might want to know how long (in milliseconds) it will take to transmit a packet of a certain type on a network interface with a given line rate in kilobits per second. To calculate this, we've defined an instance member method getTransmissionDelay() in lines 10 -- 11.

In the main() method, as an example, we've created a HELLO packet and displayed its transmission delay on a 100 kbps network interface.

enum PacketType {
HELLO (10),
JOIN (100),
ELECTLEADER (500),
LEAVE (10);
private final int size;
PacketType(int sz) {
size = sz;
}
public double getTransmissionDelay(int lineRate) {
return (double)size / lineRate;
}
}
class Application {
public static void main( String args[] ) {
PacketType p1 = PacketType.HELLO;
System.out.println(p1.getTransmissionDelay(100));
}
}

A key differentiator between classes and enums in Java is that the latter are final and hence, you cannot create type hierarchies based on your enums.

Summary

Enums are a convenient way to represent data that can take on one of a few discrete values. In Java, enums are like classes. In fact, every enum in Java is derived from java.lang.Enum. We may define instance member variables and methods in an enum, which would allow us to keep all implementation focused in one place. Enums in Java are final and you cannot create an enum derived from a user-defined enum. When representing a complex scenario, it might be worthwhile creating a class hierarchy rather than using enums.

We hope that this blog piqued your curiosity. To learn more, you might find the following courses useful:

The All-in-One Guide to Java Programming is a great course if you want to refresh your Java knowledge.

Collections in Java is a great course if you want to learn about the key collection types in Java.

Build Your Robot World in Java is a fun short course in which you will build a robot world in Java, step by step.


WRITTEN BYSaqib Ilyas