Understanding Java enums

Understanding Java enums
Reading Time: 8 minutes

Hello all, in this blog post we will study one of the strongest features of the Java language, enum’s. A good understanding of Java enum can make your program stand out of the crowd, and we will see how. We’ll establish a good understanding of the feature, learn how Java enum are different from other languages. Also, we will see the scenarios where we can implement and use enums when programming in Java. We will be doing practical analysis of the situations where enum makes life of a Java developer easy. The only prerequisite is the understanding of Object oriented programming. That said, let’s fasten our seatbelts and zoom into Java enums. As a result, I hope we will feel more comfortable with enums.

Enumerations

Enumerations in Java are available since JDK 1.5. Most of the other programming languages like C, C++, and etc also provide support for enumerations. In C++, enumerations are the user-defined data types, which has specified set of values. The variable of this type can only be assigned a value from the set of values we defined in the enum. This way we can make sure our variable expects only the valid values. For example, let’s write a small code to show how enum in c++ would work.

#include <iostream>

using namespace std;
enum board{White, Black, Sheet} displayBoard;
int main()
{
    displayBoard = Black;
    cout<<"Black = "<< displayBoard;
    return 0;
}

As we can see, in the code snippet above we created an enum: board. Variable displayBoard is now of type board. Enums work in the scenario where we have a guarantee that a field’s value can only be one of the predefined constants. Here we have defined 3 such constants {White, Black, Sheet} to represent the different board materials. As as result, values that can be assigned to displayBoard are White, Black, or Sheet. Try initializing an enum type with a different value and you will get a compilation error. The above program would print: Black = 1. It prints the ordinal value of the constant assigned to displayBoard.

What’s an ordinal value? It specifies the positioning of a constant in the enum declaration, more like indexes in arrays. In enums, ordinal value starts from 0. Thus, ordinal values for constants are White=0, Black=1, and Sheet=2. This is a very basic example to show how enums work in C++, obviously they could be used in switch statements as well. How? That, we will see when we will look at enums in Java.

Java Enums

In its simplest form an enumeration is a list of named constants, defining a new data type & its legal values. In Java, enumerations defines a class type. This greatly expands the capability of Java enums. In java, an enum can have constructor(s), methods, and instance variables like a class. Enums are created using the keyword enum. All enumerations automatically contain two predefined methods: values() and valueOf(). Their signatures are:

public static enum-type [] values()
public static enum-type valueOf(String str)

The values() method returns an array that contains a list of the enumeration constants. We can iterate through these constants to find their ordinal value or their names. The valueOf() method, on the other hand would return a enumeration constant whose value matches the passed string. Let’s quickly go through a small example to show usage of ordinal(), values(), and valueOf() methods.

In the above code snippet we have created an enum BoardMaterial, this contains the materials from which a board could be created. You can have a WhiteBoard, BlackBoard or a SheetBoard. Let’s qucikly understand what’s happening in this code block.

Workflow

Lines 9-10, we created a variable of type BoardMaterial, assigned a constant enum value to it. Thereafter, we print it out. When we print out an enum, the name of the enum is printed.

In lines 14-17, we invoked the method values() on enum BoardMaterial and iterated over its constants via foreach. We then printed the ordinal(position) value of each constant along with the constant name.

From lines 21-28, we show the usage of valueOf() method. Pay attention to the implementation here, we wrapped the invocation in a try/catch block. This is because, if the string value passed in valueOf() doesn’t match any constant in enum, it throws an IllegalArgumentException. When we run the program we see the following output on our screen:

understanding java enums, ordinal, values() and valueOf() method implementation
Basic functionality of enums, showing ordinal, values() and valueOf() method implementation.

It is important to note here, that every constant defined in an enum is a public static final member of that enum. Now, let’s see some of the advantages of using enums in such a way.

Advantages with Java enums

  • Enumeration types can only be assigned an enumeration constant in that enum type. This solves the problem of misleading/incorrect assignments to an enum. Here it would throw a compiler error, and such a mistake can be avoided at compile time itself.
  • Enumerations in Java can be compared usign == operator. Safe comparison because enum constants are static and final by default.
  • We can use enum parameter in a switch case. This makes constants driven computations better and faster.
  • Java enums can have constructor(s), instance variables and methods in them. They can even implement interfaces.
  • Enum constructors are always private.

Comparing enums

Java enums can be compared using == operator, compareTo(), or equals() method. Let’s see how the comparison using enums looks like. See the gist below:

I believe the above code is self explanatory. I would though, like to mention few important points here.

  • When we try to compare two enum variables from different enumeration using == or compareTo() method, it results in a compiler error. Though, it is possible to have equals() compare enums of 2 different type, which will always return false.
  • The equals() method would result in false, if we try to compare instances of two different enumerations that might have same ordinal value individually. Thus, by having same ordinal value doesn’t mean that the two enums are equal.
  • When we use compareTo() to compare 2 enums of same type, the result could be:
    • 0(Zero) : meaning that the ordinal value of invoking object is equal to the compared object.
    • NegativeInteger : meaning that the ordinal value of inkvoking object is less than the ordinal value of compared object.
    • Positive Integer : mening that the ordinal value of inkvoking object is greater than the ordinal value of compared object.

Here’s the output of the above code snippet.

Java enums, comaprison mechanisms.
Comparing the Java enums.

I hope by now we have gathered some good understanding of Java enums. Now, let’s look at some of the restrictions with Java enums.

Restrictions with Java Enums

  1. Though Java enums define class types, we cannot instantiate them using the new Keyword. We will see how the instantiation process works for enums in while.
  2. An Enumeration cannot inherit another class.
  3. An enum cannot act as a superclass to others. Thus, we can say that enums can’t be extended.

There’s, however, an exception to point #2 & #3 above. All enums, by default inherit the class java.lang.Enum. The methods ordinal(), compareTo(), and equals() are defined in the class Enum.

Instantiating enums

Now that we have established some good understanding of enums, let’s see how they are instantiated. We know, enums can have constructor(s), let’s examine that.

From the above code snippet, it is clear that we have created an enum Rose with the colors they are available in. Now, let us start the code analysis.

Code analysis

In the Rose enum, we have created a list of constants along with 2 instance variables, 1 constructor and a method. Every enum constant has a name, which we can get from enumInstance.name() method. Using name(), we added a print statement in the constructor, which is printed whenever enum’s instance is created. Inside the main method we created an instance of Rose enum. We also saw, how the default values to the boolean field are provided by the enum constants. Next, we traversed through constants values in the main method. Finally, we showcase the usage of enums in a switch statement. That is pretty much we did in the above code, now let’s see the output of the above program.

Java enums, constructor instantiation and switch case usage.
Java enums instantiation & switch-case usage.

We see the output includes first 5 statements saying instance of multiple constants is created. But if you remember, we created only 1 instance and that was:

Rose myRose = Rose.WHITE;

Then why would there be so many statements saying instance created? The answer is quite simple. We know that each enumeration constant is an object of it enumeration type. Thus, a constructor defined inside an enum is called for each of the constants defined in the enum. So, even when we assigned Rose.WHITE in the above statement, it invoked constructor for every enum constant. It is more important to understand that every constant maintains its own copy of the instance variables defined in a enum.

Now coming to the switch statement. We passed the myRose instance in the switch expression, and the control reached to the statement Case WHITE:. We do not have to explictly write Case Rose.WHITE. This is where Java’s type inference comes into picture.

Business use for Java enums

I hope the above example explains how Java enums work much like classes. As a result, we can now move to see a more concrete scenario where we should use enums. Consider the business use case where you write your exception classes to wrap known exceptions from the code(microservices here). Ever considered assigning error codes to your exception classes? Reason? While debugging it can be used to fetch the error messages quickly. As a matter of fact, when building large applications there’s huge error log data that is generated. Using error codes can help you classify this large data into unique chunks. Further, you can then focus on logs containing a particular error code rather than the whole pile of logs.

Let’s see how we can implement error codes in our application using Java enums.

I have created a small spring boot application with just a controller and few other necessary classes in exceptions package. The intent here is to show creating error codes using enums and then use them in our defined exceptions.

Here’s my controller, very basic one nothing special in it. I am just returning an error from the endpoint.

@RestController
@RequestMapping("/exception")
public class MyController {
    
    @GetMapping("/code")
    public Mono getErrorCodeWithException() {
        return Mono.fromCallable(() -> {
            throw new MyException();
        });
    }
}

And, here’s the MyException class that we throw from our controller.

@Getter
public class MyException extends RuntimeException {
    private static final String INTERNAL_MESSAGE = "Generated my exception with error code = ";
    public final String errorCode = ErrorCode.MY_EXCEPTION.getError();
    
    public MyException() {
        super(INTERNAL_MESSAGE);
    }
    
}

Pay special attention to the field errorCode, see how it is initialized. Now let me show you the enum that is created to store the standardized error codes.

@Getter
public enum ErrorCode {
    MY_EXCEPTION("01", MyException.class);
    
    private final String error;
    
    private final Class<? extends Throwable> tClass;
    
    ErrorCode(String code, Class<? extends Throwable> tClass) {
        this.error = "error-code::" + code;
        this.tClass = tClass;
    }
    
    @Override
    public String toString() {
        return error;
    }
}

For generating the proper error response in spring we need to have one more class annotated with @RestControllerAdvice. In our case it is

ExceptionHandlerAdvice.java

that takes care of rendering the proper error response on our screen, provided an exception occured. Here’s the class.

@RestControllerAdvice
public class ExceptionHandlerAdvice {
    
    @ExceptionHandler(value = {MyException.class})
    public ResponseEntity<String> handleMyException(
            MyException myException) {
        return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
                .body(myException.getMessage() + myException.getErrorCode());
    }
}

Output

We can hit the endpoint at http://localhost:9000/exception/code once our service has started running on port 9000. Here’s the output that you will get.

Java enum as error code in response.
Java enum as error code in the response payload.

Well, that is all we implemented in the service for generating exceptions with the error codes. Obviously, this is not the best we could have done. This is just a starting point and we can beautify the response further as per our needs & application standards. The complete code is present on the github repo. Please feel free to clone it & work on the application parallely as you read the post.

With that, I would conclude this post. Stay tuned for upcoming posts on Annotations in Java, and Record in java that came as a preview with JDK 1.14.

Conclusion

I hope this blog would help clear all your doubts regarding Java enums. It shows how Java enums are different than those in other Object oriented laguages like C++. This showcases the areas where enums should be used when programming in Java. I hope you find the content useful, and easy to understand. If you have any doubts or questions please add them in the comments section below. Please like this post if you are now comfortable with Java enums and understood the concepts I have shared above.

References

  1. Java The complete Reference (Book).
  2. https://openjdk.java.net/
  3. Orcale Docs.
Knoldus-blog-footer-image

Written by 

Prashant is a Senior Software Consultant having experience of more than 3 years, both in service development and client interaction. He is familiar with Object Oriented Programming Paradigms and has worked upon Java and Scala-based technologies and has experience working with the reactive technology stack, following the agile methodology. He's currently involved in creating reactive microservices some of which are already live and running successfully in production, he has also worked upon scaling of these microservices by implementing the best practices of APIGEE-Edge. He is a good team player, and currently managing a team of 4 people. He's always eager to learn new and advance concepts in order to expand his horizon and apply them in project development with his skills. His hobbies include Teaching, Playing Sports, Cooking, watching Sci-Fi movies and traveling with friends.