Featured enum instead of switch
Problem and its solution
Switch/case is the common control structure implemented in most imperative programming languages. Switch is considered more readable than series of if/else.
Here is a simple example:
// Switch with int literal switch (c) { case 1: one(); break; case 2: two(); break; case 3: three(); break; default: throw new UnsupportedOperationException(String.format("Operation %d is not supported", c)); }
Here is the list of the main problems in this code:
- Relationship between
int
literals (1, 2, 3) and executed code is not obvious. - If one of the values (e.g. 2) becomes not supported anymore and this switch is not updated accordingly it will contain forever the unused code.
- If new possible value of c (e.g. 4) is introduced and the switch is not updated accordingly the code will probably throw
UnsupportedOperationException
at runtime without any compile time notifications. - Such switch structure tends to be duplicated several times in code that makes problems 2 and 3 even more complicated.
The first simplest fix can be done by using int constants instead of literals. First, let’s define constants:
private static int ONE = 1; private static int TWO = 2; private static int THREE = 3;
Now the code will look like this:
switch (c) { case ONE: one(); break; case TWO: two(); break; case THREE: three(); break; default: throw new UnsupportedOperationException(String.format("Operation %d is not supported", c)); }
(Obviously in real life the names of the constants must be self descriptive)
This snippet is more readable but all other disadvantages are still relevant. The next attempt to improve the initial code snippet uses enums
introduced to Java language in version 5 in 2004. Let’s define the followingenum
:
enum Action {ONE, TWO, THREE}
Now the switch snippet will be slightly changed:
Action a = ... switch (a) { case ONE: one(); break; case TWO: two(); break; case THREE: three(); break; default: throw new UnsupportedOperationException(String.format("Operation %s is not supported", a)); }
This code is a little bit better: it will produce compilation error if one of the elements is removed fromenum Action
. However, it will not cause compilation error if additional element is added to enum Action
. Some IDEs or static code analysis tools may produce warning in this case, but who is paying attention to warnings? Fortunately enum
can declare abstract method that has to be implemented by each element:
enum Action { ONE { @Override public void action() { } }, TWO { @Override public void action() { } }, THREE { @Override public void action() { } }, public abstract void action(); }
Now the switch statement can be replaced by single line:
Action a = ... a.action();
This solution does not have any of disadvantages enumerated above:
- It is readable. The method is “attached” to
enum
element; one can write as manyjavadoc
as it is needed if method meaning is unclear. The code that calls method is trivial: what can be simpler than method invocation? - There is no way to remove
enum
constant without removing the implementation, so no unused code will remain if some functionality is no longer relevant. - New
enum
element cannot be added without implementation of methodaction()
. Code without implementation can’t be compiled. - If several actions are required they all can be implemented in enum. As we already mentioned the code that calls specific function is trivial, so now there is no code duplication.
Conclusion
Although switch/case structure is well known and widely used in various programming languages its usage may cause a lot of problems. Solution that uses java enums and described above does not have these disadvantages. The nextarticle from this series shows how to extend functionality of existing enum
.
Published on Java Code Geeks with permission by Alexander Radzin, partner at our JCG program. See the original article here: Featured enum instead of switch Opinions expressed by Java Code Geeks contributors are their own. |
how we can instanciate the Action enumeration ?
could you please give an example for the same?
Thanks
Action.ONE
@hamda, thank you for your question. I think that you mean how to get value of a in line: Action a = … Well, it is exactly as initialization of c in in initial switch/case solution: int c = … How do you initialize c? You can read its value from file, get it from other function call, from user input etc. Initialization of enum variable can be also done by string parsing sing valueOf(). Let’s say that you have properties file with the following line: action=FLY Now you can load values from properties file and then get action as… Read more »
Java 12 will improve switch syntax (https://dzone.com/articles/jdk-12-switch-statementsexpressions-in-action). If we do, what you intend, it might be easier to use:
enum Action implements Runnable {
ONE { @Override public void run() { } },
TWO { @Override public void run() { } },
THREE { @Override public void run() { } };
}
Note also, in your example, there is a comma instead of a semicolon, that make it uncompilable.
Marc, thank you for your reply. The new features of Java 12 are very interesting. It seems that java becomes closer to scala.
Unfortunately the switch still has the same weaknesses as the original switch. There is not way to inform all multiple switch/case code fragments that new Action should be supported now.
Concerning the comma – it is the enum syntax. I’ve double checked my examples and they look good for me. Please point me to concrete example that is syntactically incorrect.
Interesting idea, but if we want to tie the action to the enum, why are we even using enum, why not create a base class and implement the concrete sub classes and call the action item on it?
#Enum has some advantages.
1. The code looks better organized.
2. Each instance exists exactly once. This is guaranteed by JVM.
3. values() method allow iteration over all elements. Often it is very useful.
4. valueOf() method makes it easy to re-create enum element from string; name() does opposite operation.
I frequently have cases where I reference an ENUM but do different things depending on the context. This would work fine in cases where the ENUM only serves one limited and universal purpose. Otherwise I would find myself ignoring the method and using SWITCH statements like I do now. And I DO look for compilers warnings whenever I change an ENUM because it was an ENUM that was changed (pretty much only time I do actually).
Garry, thank you for your comment. I invested a lot of efforts to explain why switch should be never used. You do not have to use enum instead. Map between some operation ID and function does the job too. Concerning compiler warnings. I pay attention on them. However it is not always possible. 1. Eclipse has general view named “problems” and when I worked in eclipse I always kept it empty. However IntelliJ does not have such view. In order to see warnings you have to open specific class that is impossible all the time in big project. 2. Big… Read more »
I love adding functionality to enums and using enums as replacements for constants. They’re great for readability and no more need for switch() statements based on enumerated values.
Is there a “pattern” name for this? Its kinda of a cross between Singleton (there’s only 1 enum with that value) but also perhaps Strategy. Not sure. Anyways, I still use it to this day in my Java 8 coding. Can’t wait for Java 12 :-)
I usually use a Map<MyEnum,Function with a getOrDefault. But storing the code with the enum is intriguing. Removes quite some boilerplate