Enumerations (Enums) in Java

When a variable can only take one out of a small set of possible values, it's a good idea to use enums in a program. Enum is a special type of keyword; short for enumeration, that allows us to create a list of constants grouped by their content: seasons, colors, states, etc. When we store a bunch of constants in one place and handle them together, it helps us avoid errors, and makes the code look more readable and clear.

Now, let's look closer at how enums work.

Defining an enum

We can create our own enumeration in a way that is similar to declaring classes. According to the Java Code Convention, constants in an enum are written in uppercase letters. All constants should be separated with commas. Take a look at the example enum type — Season:

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER // four instances
}

It is possible to declare an enum inside a class. In this case, we don't need to use the public modifier in the enum declaration.

In general, an enum can be considered as a class with predefined instances. Here, we have four instances of seasons SPRINGSUMMERAUTUMN and WINTER inside the storage Season. If we want to extend the list of constants, we can simply add another instance in our enum: mid-winter, Australian winter, etc. Don't forget that in real life they have to make sense.

Now that we've got an idea of how to define basic enums, let's learn how to use them in a program.

Methods for processing enums

Suppose that we have to write a program with an enum that displays three possible user statuses. Let's create an enum UserStatus with these statuses:

public enum UserStatus {
    PENDING, ACTIVE, BLOCKED
}

And now, we initialize a variable of the type UserStatus, from the previous example:

UserStatus active = UserStatus.ACTIVE;

Each enum value has a name that can be accessed by using the name() method:

System.out.println(active.name()); // ACTIVE

Sometimes, we may need to access an enumeration instance by its name. This can be done with the valueOf() method which provides us with another way to initialize a variable:

UserStatus blocked = UserStatus.valueOf("BLOCKED"); // BLOCKED

An important thing to remember about this method is that it is case-sensitiveThat means that if the given string doesn't exactly match any constant, we will get an IllegalArgumentException.

UserStatus blocked = UserStatus.valueOf("blocked"); // IllegalArgumentException, valueOf is case-sensitive

If we want to look at all enumeration constants, we can get them in an array by using the values() method:

UserStatus[] statuses = UserStatus.values(); // [PENDING, ACTIVE, BLOCKED]

Another method called ordinal() returns the ordinal position of an instance of an enum:

System.out.println(active.ordinal()); // 1 (starting with 0)
System.out.println(UserStatus.BLOCKED.ordinal()); // 2

Although an enum is a reference type, two variables can be correctly compared by using both the equals method and the == operator.

System.out.println(active.equals(UserStatus.ACTIVE)); // true
System.out.println(active == UserStatus.ACTIVE); // true

Enumerations in the switch statement

An enum can be successfully used in the switch statement. Depending on the status, our program can perform different actions indicated by the switch statement. In this case, it prints out different responses:

UserStatus status = ... // some status
 
switch (status) {
    case PENDING:
        System.out.println("You need to wait a little.");
        break;
    case ACTIVE:
        System.out.println("No problems, you may pass through.");
        break;
    case BLOCKED:
        System.out.println("Stop! You can't pass through.");
        break;
    default:
        System.out.println("Unsupported enum constant.");
}

The message that our program outputs depends on the value of the status variable.

Iterating over an enum

One of the best ways to iterate over an enum is to use a for or a for-each loop. Let's apply it to our sample enum:

    for (UserStatus status : UserStatus.values()) {
        System.out.println(status);
    }
/* the output is
PENDING 
ACTIVE
BLOCKED
*/
        

Here, we used the values() method to return an array of enum values. This loop comes in handy when iterating over enums with a large number of constant values.

Fields and methods in enum

We use enums to define sets of unchangeable variables. After we defined them, we may need to extend the functionality of the enum and add values to the constants. Just like a class, an enum can have fields, constructors and methods. That's why an enum comes in handy when working with values you're not going to change.

To prove that, let's consider the following example.

Sample enum

Suppose we have to write a program that displays the battery level of a smartphone, power bank, or any device with a discrete scale.

First of all, let's create an enum with several threshold levels that represent the battery's level of charge:

public enum ChargeLevel {
    FULL, HIGH, MEDIUM, LOW
}

Suppose that we need to display the level of battery charge visually. We want it to be divided into several segments and have a color indication as well, in this way:

Illustration of different battery levels

To do this, we will add corresponding fields and values to our enum. When we define them, we must supply values to the constructor of the enum. Here, we created a constructor in the ChargeLevel enum and added two fields: sections and color. Also, there are two methods getSections() and getColor() that return the values of fields respectively.

public enum ChargeLevel {

    FULL(4, "green"),
    HIGH(3, "green"),
    MEDIUM(2, "yellow"),
    LOW(1, "red");

    private final int sections;
    private final String color;

    ChargeLevel(int sections, String color) {
        this.sections = sections;
        this.color = color;
    }

    public int getSections() {
        return sections;
    }

    public String getColor() {
        return color;
    }
}

Note that all enum instances are created by the JVM in the same way as a static field of a class. This is the reason why an enum cannot contain a public constructor. This means we cannot create enum objects by invoking an enum constructor with the new keyword, but have to choose one of the predefined instances instead. Adding private access modifier to constructor doesn't make an effect.
Keep in mind that if your enum contains fields and methods, you should always define them after the list of constants in the enum.

Now, we have a class with additional info gathered in one place: the number of sections to highlight and the color.

System.out.println(ChargeLevel.LOW.getSections()); // 1
System.out.println(ChargeLevel.LOW.getColor()); // red

It is possible to extend an enum by adding custom static methods. For example, let's add a method that finds a ChargeLevel instance by the given number of sections:

public enum ChargeLevel {

    FULL(4, "green"),
    HIGH(3, "green"),
    MEDIUM(2, "yellow"),
    LOW(1, "red");

    private final int sections;
    private final String color;

    ChargeLevel(int sections, String color) {
        this.sections = sections;
        this.color = color;
    }

    public int getSections() {
        return sections;
    }

    public String getColor() {
        return color;
    }

    public static ChargeLevel findByNumberOfSections(int sections) {
        for (ChargeLevel value: values()) {
            if (value.sections == sections) {
                return value;
            }
        }
        return null;
    }
}

Inside the findByNumberOfSections() method, we iterated over the possible values using a for-each loop. Here's an example of our method's output:

System.out.println(ChargeLevel.findByNumberOfSections(2)); // MEDIUM

Enumset

EnumSet is a specialized implementation of the Set interface that extends AbstractSet and is used with enum types in Java. If you are planning to use enums, then EnumSet is the right choice. You can see the hierarchy of this class in the following diagram:

EnumSet hierarchy

Creating an EnumSet

EnumSet is an abstract class, so we cannot directly create its object. It means that in order to use EnumSet, the implementation provides various factory methods that come in handy. To use EnumSet in a program, we need to write import java.util.EnumSet;.

Consider an enum class named BallsColor that contains a const enum and depicts various colors of balls.

enum BallsColor {
        RED, GREEN, BLUE, YELLOW, ORANGE
}

Visually, we can represent it as a box containing all the enum constants. Here, the constants are represented as balls of the respective color.

Balls colors image

To create an EnumSet, we can use the static factory methods. It is important to note that all the static factory methods every time return a new EnumSet of the same type.

Now, let's discuss some of the static factory methods in detail.

Static factory methods

  • of()

If we want a new box of the same dimensions with a different identity (in other words, an EnumSet of the same type) with certain color constants, we should use the of() method and pass the required enums as an argument. We can pass any number of arguments.

Notice how the of() method creates a new box of the same dimension (that is, the same type) but with a different identity (because a box with a blue lid is an enum class and the box with a dark blue lid is an EnumSet).

Balls colors

This is what it looks like syntactically:

EnumSet<BallsColor> colors2 = EnumSet.of(BallsColor.GREEN, BallsColor.BLUE);
System.out.println(colors2); // [GREEN, BLUE]

For the rest of this topic, every new "box" will represent a new EnumSet. Also, consider "the same dimension" as the same type as the one specified on the left-hand side.

  • allOf()

If we need a new box with all the enums in the BallsColor class, we can use the allOf() method to create it. Here, we pass the enum class as an argument.

Balls colors and color set

This is what it looks like syntactically:

EnumSet<BallsColor> colorsSet = EnumSet.allOf(BallsColor.class);
System.out.println(colorsSet); // [RED, GREEN, BLUE, YELLOW, ORANGE]
  • noneOf()

If we want to have an empty box of the same dimension, we can use the noneOf() method that will essentially create a new empty box for us. Here, we pass the enum class as an argument.

Balls colors and colors set image

Syntactically:

EnumSet<BallsColor> colorsSet = EnumSet.noneOf(BallsColor.class);
System.out.println(colorsSet); // []
  • complementOf()

Suppose, we already have a box named color2 with just blue and green in it. To have a new box with all the colors except the ones in color2, we can use the complementOf() method.

Balls colors and new colors set

Syntactically:

EnumSet<BallsColor> newColorsSet = EnumSet.complementOf(colors2); // [GREEN, BLUE]
System.out.println(newColorsSet); // [RED, YELLOW, ORANGE]
  • range()

This method takes two parameters: the first enum and the last enum, and returns an EnumSet of all the enums between them, including the specified two.

EnumSet<BallsColor> ballsColors = EnumSet.range(BallsColor.RED, BallsColor.YELLOW);
System.out.println(ballsColors); // [RED, GREEN, BLUE, YELLOW]

It is important to note that the type of EnumSet should be the same as that of enum const being passed in various static factory methods. EnumSet implements the Set interface so we can use all its operations/methods.

Here is an example:

enum Status {
    RECEIVED, PROCESSING, CHECKING, DISPATCHED, PAYMENT_COLLECTION
}

EnumSet<Status> statuses = EnumSet.range(Status.RECEIVED, Status.DISPATCHED);
statuses.remove(Status.CHECKING); // true
statuses.remove(Status.DISPATCHED); // true

statuses.add(Status.CHECKING); 

// Notice how Checking Enum is placed at the correct position 
// as in Status class in spite of adding it afterwards
System.out.println(statuses); // [RECEIVED, PROCESSING, CHECKING]
statuses.contains(Status.PROCESSING); // true

There are many other operations in Java that you can use with HashSet or Set in general.

If you pay attention, EnumSet also teaches you a couple of good design practices to create flexible and maintainable code!

RegularEnumSet and JumboEnumSet

Remember, EnumSet is just an abstract class, which means the actual logic and computation are implemented by the subclasses called RegularEnumSet and JumboEnumSet.

Abstract EnumSet class

The difference is that JumboEnumSet is selected when the size of the enum passed is more than 64. RegularEnumSet uses a single long to represent the bit vector. Each bit of the long element represents a value of the enum. The i-th value of the enum will be stored in the i-th bit, so it's quite easy to know whether a value is present or not. Since long is a 64-bit data type, this implementation can store up to 64 elements, whereas JumboEnumSet uses an array of long type elements for storing purposes.

Characteristics of EnumSet

  1. EnumSet is internally represented in the bit vector. It combines the end performance of the bit field with the many advantages of enum types.
  2. Due to the above-mentioned implementation, EnumSet is faster than HashSet (though it is not guaranteed).
  3. EnumSet cannot be used to store any other object except enums. At the same time, you cannot store instances of two different enums.
  4. EnumSet doesn't allow null elements.
  5. It is a mutable set. We can make it immutable by using Collections.unmodifiableSet. Remember, the final keyword cannot be used to create an immutable data structure. You can refer to this StackOverflow question.

Conclusion

The enum keyword helps us to define named constants grouped together according to their content. By defining enums, you can make code more readable and avoid invalid values being passed in.
The number of constants in an enum may be extended whenever we want. Also, you can use the name()valueOf()ordinal() and equals() methods to process the enum. switch statements and for-each loops are widely used while working with enums in simple programs.

Since an enum is a special class type in Java, we can add constructors, fields, and methods to it. Thus, it is possible to enhance our enum to include the values we need. The values of the constants are defined when we declare the enum. If you want to add enum fields, methods and constructors, you should do it after the enum constants' declaration.

As a rule of thumb, if you want to use a set of enums, EnumSet should be your first choice unless stated otherwise. The internal implementation uses bit arithmetic, which is a powerful tool that makes the representation very compact and efficient with enums. The EnumSet class has been divided into two package-private classes depending on the number of enums passed. EnumSet provides static factory methods to interact with it. We can create an immutable EnumSet by wrapping it with Collections.unmodifiableSet at the cost of performance.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate