Java enums are very powerful and important part of the Java language. Even though they can do so much, it is very common to see them either being used incorrectly or not used at all. In this blog post I want to remind you what Java enums can do and show some of the usage patterns that don’t get enough attention.
What are Java enums?
An enumerated type in Java is a type whose values consist of a fixed set of constants. This sounds quite theoretical, so let’s try with an informal way of describing them. In Java, enumerated types are kinds of classes which have predefined set of instances. This is quite different to C# where enums are a much simpler type. In Java enums can have constructors, methods, fields… They really are like classes, with only a few specific differences:
- They extend java.lang.Enum
- They have fixed set of instances
- They can be used in switch statements
- They can’t be extended
- They have minor differences that won’t be important for the usages mentioned here unless explicitly stated
With this reminder of how similar enums are to classes, it should be clear that they can be used for more than simply enumerating things.
Enums are great as Singletons
Using Enums in Java it is very easy to implement a lazy loading Singleton:
public enum Singleton {
INSTANCE;
private Singleton() {
//This is called the first time this enum is initialised
System.out.println("I am initialised");
}
}
As you can see, this provides a very simple and correct implementation of the singleton pattern. Also, since this is an enum, it does not have to be a single instance. There could be precisely two instances etc.
Enums can replace switch statements
This is one of my favourite uses of enums and one that is often overlooked. It is often the case that someone uses enum for selecting different behaviour. It is common to see a code like this:
public static boolean isTransactionComplete(Transaction transaction){
switch(transaction.getTransactionState()){
case COMPLETE:
return true;
case PENDING:
return true;
case REJECTED:
return false;
default:
return true;
}
}
with a TransactionState
enum like this:
public enum TransactionState {
COMPLETE, REJECTED, PENDING
}
This is ok, but what if the same method (with the same business logic) is needed somewhere else? You can always move the method inside the enum and have it constructed like this:
public enum TransactionState {
COMPLETE, REJECTED, PENDING
public boolean isTransactionComplete(){
switch(this){
case COMPLETE:
return true;
case PENDING:
return true;
case REJECTED:
return false;
default:
return true;
}
}
}
This at least keeps the logic together with the definition of the enum, so it is all in one place (encapsulation). However, if the enum
gets large and complicated (never through our fault!) it is easy to introduce bugs. Let’s add
another state- AWAITING_APPROVAL
which is not transactionComplete. If someone overlooks changing the
isTransactionComplete
method, a new bug is introduced. This can be prevented by introducing some type-safety. Java
enums can declare abstract methods which have to be implemented by specific instances. Let’s introduce isTransactionComplete
as an abstract method and refactor this enum as follows:
public enum TransactionState {
COMPLETE {
@Override
public boolean isTransactionComplete() {
return true;
}
}, REJECTED {
@Override
public boolean isTransactionComplete() {
return true;
}
}, PENDING {
@Override
public boolean isTransactionComplete() {
return false;
}
}, AWAITING_APPROVAL {
@Override
public boolean isTransactionComplete() {
return false;
}
};
public abstract boolean isTransactionComplete();
}
This adds a few more brackets, but no new instance can be added without explicitly stating what the implementation of
isTransactionComplete
method should be. I worked on a project where we inherited code with dozens of enum instances and many
switch statements across the code. If this pattern was followed, it would have been much easier to understand
what is required when adding a new enum instance- compiler would have told us! Good use of enums creates a code that
can help developers extend it, without changing any implemented methods. This is called the open/closed principle.
Enums with interfaces can provide extensibility
I have learned this trick from Effective Java (which is featured in my Recommended Reading for Java Developers)
and it shows how to elegantly overcome one of the enum limitations. As mentioned in the beginning of this blog post, enums
can’t be extended. Most of the time extending enums is a bad idea, but there are some cases where it does make sense.
Imagine that you are writing a library where you want to provide clients with some list of enums that can be used in your library-
let’s say MathematicalMean
. At the moment you are using this enum:
public enum MathematicalMean {
SIMPLE_MEAN {
@Override
public double calculate(double[] values) {
double mean = 1.0;
for(double d : values) {
mean += d;
}
return mean / values.length;
}
}, GEOMETRIC_AVERAGE {
@Override
public double calculate(double[] values) {
double product = 1.0;
for(double d : values) {
product = product * d;
}
return Math.pow(product, 1.0 / values.length);
}
};
public abstract double calculate(double[] values);
}
Your library provides a function: executeAlgorithm(double[] data, MathematicalMean mean)
, this is a strategy pattern
in action. If the client wants to use harmonic mean in the algorithm, then he is out of luck- enums can’t be extended. The
way to fix it, is to use an interface rather than an enum- something that is instinctively done when working with
classes. Let’s have a following Interface and Enum provided by the library:
public interface MathematicalMean {
public abstract double calculate(double[] values);
}
and the enum:
public enum BasicMean implements MathematicalMean {
MEAN_AVERAGE {
@Override
public double calculate(double[] values) {
double mean = 0.0;
for(double d : values) {
mean += d;
}
return mean / values.length;
}
}, GEOMETRIC_AVERAGE {
@Override
public double calculate(double[] values) {
double product = 1.0;
for(double d : values) {
product = product * d;
}
return Math.pow(product, 1.0 / values.length);
}
};
}
Now the client is free to implement its own AdvancesMean
enum with the HARMONIC_MEAN
member and the library is still
able to provide implementation for basic mathematical means. For some programmers
this may look like a non-standard case, but it actually comes up frequently in some domains. I have used it when working
on algorithmic library with a requirement for clients to be able to extend the algorithm.
It is useful to know when mixing strategy pattern and enums in general.
Final Words
Enum in Java is a very powerful construct. There is a lot of attention on the new Java features and what they bring, but very often we are still under utilising the older set of language features. I think Java enums are one of the greater strengths of the language and I encourage you to use more of them in your code. Good luck in your implementations!