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 SPRING
, SUMMER
, AUTUMN
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-sensitive. That 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:
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:
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.
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
).
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.
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.
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.
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
.
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
EnumSet
is internally represented in the bit vector. It combines the end performance of the bit field with the many advantages of enum types.- Due to the above-mentioned implementation,
EnumSet
is faster thanHashSet
(though it is not guaranteed). EnumSet
cannot be used to store any other object except enums. At the same time, you cannot store instances of two different enums.EnumSet
doesn't allow null elements.- It is a mutable set. We can make it immutable by using
Collections.unmodifiableSet
. Remember, thefinal
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.