Getting rid of switch statements with Java Enums
I recently saw an interesting and polymorphic way to get rid of using a case statement when using enums. This is possible by defining a method for each instance of an enum.
I’m sure that you have seen code like this:
-
enum Friend {
-
Joey, Chandler;
-
}
And then somewhere in the code, you might see:
-
class SomeObjectThatNeedsToKnowBestFriends {
-
-
void doSomethingWithBestFriends() {
-
for (Friend friend : Friend.values()) {
-
doSomething(bestFriend(friend));
-
}
-
}
-
-
Friend bestFriend(Friend friend) {
-
switch (friend) {
-
case Joey: return Friend.Chandler;
-
case Chandler: return Friend.Joey;
-
}
-
}
-
}
This is a common smell in a code base where client code has logic that should be better encapsulated. Now, whenever someone adds a friend, they are going to have to search for references on Friends and add a new entry, or they are going to get a RuntimeException from the switch statement. In a well tested codebase, there is probably going to be a unit test that asserts that all Friends have best friends. In any case, the switch statement in the client object code is not great from an OO standpoint and it creates a maintainability issue.
The first refactoring is to move all Friend related logic to the Friend enum where it belongs.
-
enum Friend {
-
Joey, Chandler;
-
-
Friend bestFriend() {
-
switch (this) {
-
case Joey: return Chandler;
-
case Chandler: return Joey;
-
}
-
}
-
-
}
Now, we can just ask the Friend who their best friend is.
-
Joey.bestFriend(); –> returns Chandler;
Nice. Still, I’m not wild about that switch statement. Developers still have to know to update it, and really, I’d rather not even have to throw an exception because it was misused. It would be better if the structure of the code did not allow misuse.
Here is an example of how to do this:
-
enum Friend {
-
Joey {
-
Friend bestFriend() { return Chandler; }
-
},
-
Chandler {
-
Friend bestFriend() { return Joey; }
-
}
-
abstract Friend bestFriend();
-
}
Then,
-
Joey.bestFriend(); –> returns Chandler;
Great, now we know that when someone adds a new friend, they will immediately be confronted with having to supply a best friend. My only issue with this approach is that all the method definitions become verbose when you introduce many methods like this. I tried different approaches to solving this problem, but due to the enums referencing each other, I was not able to do a different approach.
Here is an example of what you can’t do:
-
enum Friend {
-
Joey (Chandler),
-
Chandler(Joey)
-
-
final Friend bestFriend;
-
-
Friend(Friend bestFriend) {
-
this.bestFriend = bestFriend;
-
}
-
-
Friend bestFriend() {
-
return bestFriend;
-
}
-
}
This won’t work because you can’t reference Chandler in the enum definition for Joey. The Chandler Enum hasn’t been defined yet, so this won’t even compile. However, you can “trick” the compiler by fully referencing Chandler using Friend.Chandler;
-
enum Friend {
-
Joey (Friend.Chandler),
-
Chandler(Joey)
-
-
final Friend bestFriend;
-
-
Friend(Friend bestFriend) {
-
this.bestFriend = bestFriend;
-
}
-
-
Friend bestFriend() {
-
return bestFriend;
-
}
-
}
However, the result is not what we want:
Joey.bestFriend(); –> null
Chandler.bestFriend(); –> Joey
Even though I can reference the other enum instance this way, it resolves to null. The reason lies in the fact that when the Enum is compiled, each instance is a static final field, and initialized in a static block. Here is a snippet of the generated code:
-
public static final Friend Joey;
-
public static final Friend Chandler;
-
final Friend bestFriend;
-
private static final Friend ENUM$VALUES[];
-
-
static
-
{
-
Joey = new Friend(“Joey”, 0, Chandler);
-
Chandler = new Friend(“Chandler”, 1, Joey);
-
ENUM$VALUES = (new Friend[] {
-
Joey, Chandler
-
});
-
}
Interestingly, I can write a program that tries the fully qualified name for explicit static constants, and it works:
But if I use a static initialization, it doesn’t:
Interesting. The compiler is smart enough to resolve the correct value when you don’t use a static initialization block. Back to my original example with Friends. The next attempt was to create an anonymous constructor (actually, an instance initializer) for the enum instances and see if I could get what I want:
-
enum Friend {
-
Joey {
-
{
-
bestFriend = Chandler;
-
}
-
},
-
Chandler {
-
{
-
bestFriend = Joey;
-
}
-
}
-
-
Friend bestFriend;
-
-
Friend bestFriend() {
-
return bestFriend;
-
}
-
}
I had to remove the final from bestFriend, since I’m inializing the value when the object is instantiated using an instance initializer. This compiles, and seems like an okay approach. My hope was that the references to other enum types would get resolved in much the same manner as in the case of creating a method that returns each one. Interestingly, this doesn’t happen.
-
Joey.bestFriend(); –> null
-
Chandler.bestFriend(); –> Joey
The reason is that even though I am using an instance initializer, it’s being called from a static block since the instances are created in a static block. Turns out to be a naive attempt. Here is what it ends up looking like when the enum gets generated as a class:
-
static
-
{
-
Joey = new Friend(“Joey”, 0) {
-
-
-
{
-
bestFriend = Friend.Chandler;
-
}
-
}
-
;
-
Chandler = new Friend(“Chandler”, 1) {
-
-
-
{
-
bestFriend = Friend.Joey;
-
}
-
}
-
;
-
ENUM$VALUES = (new Friend[] {
-
Joey, Chandler
-
});
-
}
Oh well. That’s as far as my experimentation went. I’m satisfied that I can at least create an anonymous subtype of an enum that returns the correct value, but if anyone has any ideas on how to do this in a cleaner way, let me know.
Tags: instance initializers, java, Java enums, polymorphism, static initializer
October 20th, 2008 at 6:31 am
Great post. I’ve had a similar problem with enums but I didn’t dig so deep why forward referencing doesn’t work.
enum Friend {
Joey(Friend.Chandler),
Chandler(Joey);
…
}
won’t compile with Sun compiler (jdk1.6_06). So using anonymous enums is probably the only way to go.
July 21st, 2009 at 12:22 am
good article. I don’t know how you got the generated code for Enum but it is useful to see how Enum are treated internally. Makes it very clear that each enum is a full fledged object instance.
July 22nd, 2009 at 8:05 pm
Thanks Vivek! I generated the code using Jad - decompiler for eclipse. It became hard to find for a while since the main website went down, but there are mirrors for it now. Check out: http://www.varaneckas.com/jad
September 3rd, 2009 at 10:36 am
Cool site, love the info.
April 15th, 2010 at 3:04 pm
Very useful, a great natural design for using enums.
Also, this is not a big deal, but in order to compile your example I had to add a semi-colon at the end of line 7.
1.enum Friend {
2. Joey {
3. Friend bestFriend() { return Chandler; }
4. },
5. Chandler {
6. Friend bestFriend() { return Joey; }
7. }
8. abstract Friend bestFriend();
9.}