Enums - one of the underrated features of Java
I remember when I was first learning programming and stumbled upon enums, my first reaction was "*this is cute, but is it really useful for something?*" and "*do people really use this thing?*".
I remember when I was first learning programming and stumbled upon enums, my first reaction was "this is cute, but is it really useful for something?" and "do people really use this thing?". Well, coming to present time, I can answer that yes, they are really useful, and that probably people do not use them as much as they should.
For a while know, I became more and more amazed on how useful enums are as a concept and in how many ways they can be used in practice and today I decided to share with you my thoughts on it.
Let's see what enums are and more importantly how can you use them. As a warning I will be focusing in my examples on Java, but I am sure at least some of the use cases can be applied to other programming languages.
What is an enum?
In Java enum is short for Enumerated type and in other parts it is also known as enumeration, factor (e.g. R language) or categorical variable in statistics (yes it is also present in Math, actually it kinda comes from there). Although not exactly the same in functional languages, in that paradigm they can be considered a degenarated form of tagged unions or sum types.
They are data types that consist of a set of named values called either elements, members or enumerators of the type.
Basically they define a type of which only certain values are possible, values which have a name (or tag) associated with them. A variable declared to be of an enum type can only be assigned values that are predefined in the definition of that enum.
Specific to Java, an enum has quite an extended behavior: it is like a class that can have only a fixed set of instances and this fixed set is hardcoded in the source code that defines the enum. We will come back to this later to see why it is useful.
If this sounds too abstract, let us give an example that will introduce also the first use case for enums.
The world without enums
Let's imagine that we have a function that accepts as a parameter a day of the week. One naive way to design this would be to have that parameter as a String type:
public void function(String dayOfWeek){
//do stuff
}
And a user would call it like this:
// some logic
function("Thursday");
// some other logic
One big problem with this is that a user could pass anything to our function as a parameter, not just the name of a weekday:
// some logic
function("banterly");
// some other logic
A user could either be making a typo, pass in a wrong value or just do it because he is not clear on what he should pass as a value to the function (e.g. should it be the day of the week with a capital or without?). Either way, he has no way of knowing if he passed a bad value until runtime, when hopefully the function will have some sort of validation on the input and throw an exception. In the worst case there is no validation and incorrect behavior can propagate undetected in the flow of the program.
Another serious implication is performance degradation: if the function has to do a lot of equality checks with the received parameter (which it will have to do if it employs some sort of validation to check that the value is indeed a weekday), then a lot of string comparisons will have to take place which are not very beneficial for performance critical applications.
I am already hearing some of you guys saying: "wait a minute sir, we can use the following idiom for this use case: define a set of int constants that would represent the days of the week":
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
public static final int SUNDAY = 7;
public void function(int weekOfDay){
//do stuff
}
//call it later like this
function(SUNDAY);
This solves the performance problem and it allows the users to pass in predefined values in a relatively safe manner, but notice that I said it allows, not that it forces them: users can still pass anything to the function and make it behave in an incorrect way. Not only this, but this idiom does not introduce any namespace for the defined values which can lead to name conflicts like the following scenario:
//traffic lights
public static final int GREEN = 0;
public static final int ORANGE = 1;
public static final int RED = 2;
//for fruits
public static final int PEAR = 0;
public static final int ORANGE = 1; // will give a compile error since ORANGE is already defined above
public static final int APPLE = 2;
If defined in the same class the above will not compile, so you will have to prefix each name with some sort of identifier:
public static final int TRAFFIC_LIGHT_ORANGE = 1;
public static final int FRUIT_ORANGE = 1;
Another issue with these definitions is that a function can now accept either a fruit or a traffic light and produce the same output which does not make sense and the compiler has no way to complain about this.
I hope this entire essay convinced you why it is not a good idea to use any of the two described approaches. So what is the solution? While of course the subject of this article.
Using enums - Empowering the user and the compiler
Let us define a very simple enum that will represent the days of the week.
enum Weekday {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
Now we can define our function to take a parameter of type Weekday:
public void function(Weekday weekday){
//do stuff
}
// later on we call it
function(Weekday.TUESDAY);
This will mean that a user can only pass in one of the values defined for the Weekday enum, otherwise the compiler will complain. This comes with the following benefits:
- No risk of the user accidentally typing a wrong input or a malicious user intentionally doing so.
- The user knows what he needs to pass in by just looking at the signature of the method.
- Another benefit over the previous two methods is that the enum types give you access for free on iterating over their possible values in case you ever need it.
- Using enums opens the door for using EnumSet and EnumMap, two type safe and performant implementations of the Set and Map interfaces, otherwise you would have to do some ugly things to satisfy certain usecases. Will give an example for EnumSet here and leave you to discover the EnumMap yourself. If a function requires possibly a combination of input values, then if you are using the constant int pattern you would have to assign powers of 2 to your values and then do bitwise operations on the values:
//obsolete idiom
public class Renderer{
public static final int COLOR_RED = 1;
public static final int COLOR_BLUE = 2;
public static final int COLOR_ORANGE = 4;
public static final int COLOR_GREEN = 8;
public void draw(int colors){
// draw
}
}
Calling the draw function for colors blue and red would be something like this
renderer.draw(COLOR_RED | COLOR_BLUE);
With an EnumMap you do not have to apply any of those tricks, things just work and besides that you have type safety and the performance that comes with these structures:
public class Renderer{
public enum Color{RED, BLUE, GREEN, ORANGE}
public void draw(Set<Color> colors){
//draw
}
}
We now can call the draw method like this:
renderer.draw(EnumSet.of(Color.RED, Color.BLUE))
- Enum values are by definition the only instances created of the enum type, guaranteed by the JVM, so if your logic needs to compare enum values you can safely use the == operator to perform a reference equality check. It is the fastest way to compare two objects and also no need to worry about null values(if you would use the equals method, which by default just defers to reference comparison anyway, you would have to worry about the order of the operators in order to avoid possible null pointer exceptions).
- It helps the compiler identify attempts to compare incompatible types which can avoid programming errors that would be identified only at runtime:
enum Weekday {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
enum Color{BLACK, WHITE};
if(Color.BLACK.equals(Weekday.Monday); // does not throw a compile error since the equals signature specifies Object as the parameter type
if(Color.BLACK == Weekday.Monday); // does not compile because the compiler can detect the incompatible types attempting to be compared by reference
So to recap, if your function or logic needs to use a parameter or variable of a type that has only certain limited possible values, use an enum, that will make everyone happy.
The curious case of the Singleton
The singleton pattern is used to ensure that a type of object is instantiated once and only once.
The classical best practice approach is to use something like the following:
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return INSTANCE;
}
public void doSomething(){
//do stuff
}
//other methods ...
}
While this works, there are some issue:
- If you want your singleton class to be serializable you need not only have it implement the Serializable interface, but also the readResolve method that will return the singleton value otherwise new instances can be created(this is explained a little more further on)
- The class could still be instantiated with a new instance through the use of the Reflection API.
In light of these issues Joshua Bloch recommends, in his famous book Effective Java, an even better way of doing things. Since enums guarantee that only the values listed in its definition will ever be instantiated, then a single value enum can act as a singleton. That is possible because in Java the enum allows users to do things similar with what they can for a class definition: you can define methods, fields and even implement interfaces. One of the only things you cannot do with an enum is extend other classes so if you singleton has this requirement then this solution will not work for you.
So our example from above would become:
public enum Singleton{
INSTANCE;
public void doSomething(){
// do stuff
}
// other methods
}
First of all this looks a lot cleaner right? Now how does this solve the 2 problems lister earlier. Let's see:
- I will not enter in all the details of how serialization in Java works, but usually when deserializing an object, the deserializer creates a new instance of the object being deserialized with the values from the serialized form. That is why we have to implement the readResolve method which gets called by the deserializer to provide the opportunity to the deserialized object itself to specify what object to return. This is mostly used for handling singletons since the object to be returned is actually in one of its fields and we do not want a new instance since that would break the Singleton property. But enum serialization and deserialization work differently than for normal java objects. The only thing that gets serialized is the name of the enum value even if it has other fields defined. Then on the deserialization side the name is read and since the enum type can have only the values specified in its definition, the enum valueOf method is used with the deserialized name to get the desired instance. So in short, using enums gives you free serializable behavior.
- The enum type actually extends the java Enum class. The reason that reflection cannot be used to instantiate objects of enum type is because the java specification disallows and that rule is hardcoded in the implementation of the newInstance method of the Constructor class, which is usually used for creating objects via reflection:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
So as a best practice, unless one of the caveats from above applies to you, use an enum for you singletons.
A strategy for Strategy
One of the latest things I realized an enum is actually really useful for is implementing the strategy pattern.
Lets have a quick recap of the pattern: The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The pattern allows the algorithm to vary independently from clients that use it.
If that definition sounds to abstract let's showcase its classic implementation with a simple example.
First we need to identify a case where we could apply this pattern:
Let us imagine we have a bar and since we want to attract customers we have different pricing strategies depending on the time of the day (if when describing the problem you start using the strategy word its a good sign you might consider applying this pattern):
- we have the normal hours where clients are charged the full price of what they consume
- we have the morning happy hour where clients are charged 0.5 of what they consume
- we have the afternoon happy hour where clients are charged 0.75 of what they consume.
So we define an interface that encapsulates this logic(don't ever use double for this kind of calculations, using them here just for simplicity):
interface BillingStrategy{
double bill(double rawPrice);
}
Then we define different classes that implement the BillingStrategy interface with specific behavior:
public MorningHappyHourBilling implements BillingStrategy{
double bill(double rawPrice){
return rawPrice * 0.5;
}
}
public EveningHappyHourBilling implements BillingStrategy{
double bill(double rawPrice){
return rawPrice * 0.75;
}
}
public FullPriceBilling implements BillingStrategy{
double bill(double rawPrice){
return rawPrice ;
}
}
Then we define a BillCalculator that would be used by the bar:
BillCalculator {
private BillingStrategy billingStrategy = new FullPriceBilling() ;
public void setBillingStrategy(BillingStrategy billingStrategy){
this.billingStrategy = billingStrategy;
}
public void printBill(int price, String customer){
System.out.format("Bill for customer %s is %f", customer, this.billingStrategy.bill(price);
}
}
As you can see, now the hypothetical Bar class when using the BillCalculator can modify during runtime the behavior of the calculator whenever it needs, in this case whenever the happy hour interval is present.
Now you might say: "this is nice and all, but it brings quite some overhead":
- we have to define a new interface
- we need to define a new class for each possible strategy,
You are right, usually thats the tradeoff when using design patterns, they provide flexibility at the expense of more abstraction overhead. Luckily for this pattern, we can actually do better by using our old friend the enum.
Instead of defining an interface, we will define an enum as the type of the BillingStrategy and its values will be the 3 different billing strategies:
public enum BillingStrategy{
MORNING_HAPPY_HOUR{
@Override
public double bill(double price){
return rawPrice * 0.5;
}
},
AFTERNOON_HAPPY_HOUR{
@Override
public double bill(double price){
return rawPrice * 0.75;
}
},
FULL_PRICE{
@Override
public double bill(double price){
return rawPrice;
}
}
public abstract double bill(double price);
}
Now we can redefine our BillCalculator like this:
BillCalculator {
private BillingStrategy billingStrategy = BillingStrategy.FULL_PRICE;
public void setBillingStrategy(BillingStrategy billingStrategy){
this.billingStrategy = billingStrategy;
}
public void printBill(int price, String customer){
System.out.format("Bill for customer %s is %f", customer, this.billingStrategy.bill(price);
}
}
A lot cleaner right?
As an added bonus, if you are familiar with the State pattern you know that its implementation is basically the same as the Strategy pattern, it differs only in intent and some other specific details, so as a consequence we can also implement that one using the enum technique we showcased above.
Conclusion
That is all I have for now to share and I hope at least some of you discovered something new reading this.
If you have some other great use cases for enums or some insights about them from other languages, let me know.
If you enjoyed the article you can subscribe or like our facebook page(do not worry about spam, I write something or post every few months if that):
https://www.facebook.com/pg/banterly.net/posts/