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.
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.
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.
Here's a Java program that represents this flow:
class Course {// Represents the current stage of the course creationint courseStage;// The proposed course nameString courseName;// Constructor for the coursepublic Course(String name) {courseName = name;courseStage = 0;}// Approve a coursevoid approve() {if (courseStage != 0) {System.out.println("Course is already approved!");return;}courseStage = 1;}// Send a course for proofreadingvoid sendForProofreading() {if (courseStage != 1) {System.out.println("Only a course in creation stage can be sent for proofreading!");return;}courseStage = 2;}// Launch a coursevoid 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 coursepublic 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:
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).
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.
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:
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.
Suppose that we decide to insert a new stage, say "author search", to the pipeline.
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.
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.
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 creationCourseStage courseStage;// The proposed course nameString courseName;// Constructor for the coursepublic Course(String name) {courseName = name;courseStage = CourseStage.IDEATION;}// Approve a coursevoid approve() {if (courseStage != CourseStage.IDEATION) {System.out.println("Course is already approved!");return;}courseStage = CourseStage.CREATION;}// Send a course for proofreadingvoid 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 coursevoid 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 coursepublic 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);}}
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.
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.
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.
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.
Free Resources