What is the usage of default when the switch is for an enum?
JavaEnumsJava Problem Overview
Suppose I have an enum Color
with 2 possible values: RED
and BLUE
:
public enum Color {
RED,
BLUE
}
Now suppose I have a switch statement for this enum where I have code for both possible values:
Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
...
break;
case BLUE:
...
break;
default:
break;
}
Since I have code block for both possible values of the enum, what is the usage of default
in the above code?
Should I throw an exception if the code somehow reaches the default
block like this?
Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
...
break;
case BLUE:
...
break;
default:
throw new IllegalArgumentException("This should not have happened");
}
Java Solutions
Solution 1 - Java
It is good practice to throw an Exception as you have shown in the second example. You improve the maintainability of your code by failing fast.
In this case it would mean if you later (perhaps years later) add an enum value and it reaches the switch statement you will immediately discover the error.
If the default value were not set, the code would perhaps run through even with the new enum value and could possibly have undesired behavior.
Solution 2 - Java
The other answers are correct in saying that you should implement a default
branch that throws an exception, in case a new value gets added to your enum in the future. However, I would go one step further and question why you're even using a switch
statement in the first place.
Unlike languages like C++ and C#, Java represents Enum values as actual objects, which means that you can leverage object-oriented programming. Let's say that the purpose of your method is to provide an RGB value for each color:
switch (color)
case RED:
return "#ff0000";
...
Well, arguably, if you want each color to have an RGB value, you should include that as part of its description:
public enum Color
{
RED("#FF0000"),
BLUE("#0000FF");
String rgb;
public Color(String rgb) {
this.rgb = rgb;
}
public getRgb() { return this.rgb; }
}
That way, if you add a new color later, you're pretty much forced to provide an RGB value. It's even more fail-fast than the other approach, because you'll fail at compile-time rather than run-time.
Note that you can do even more complicated things if you need to, including having each color provide its own custom implementation of an abstract method. Enums in Java are really powerful and object-oriented, and in most cases I've found I can avoid needing to switch
on them in the first place.
Solution 3 - Java
Compile time completeness of the switch cases doesn't guarantee runtime completenes.
Class with a switch statement compiled against an older version of enum may be executed with a newer enum version (with more values). That's a common case with library dependencies.
For reasons like these, the compiler considers the switch
without default
case incomplete.
Solution 4 - Java
In small programs, there is no practical use for that, but think of a complex system that speards among large number of files and developers - if you define the enum
in one file and use it in another one, and later on someone adds a value to the enum
without updating the switch
statement, you'll find it very useful...
Solution 5 - Java
If you've covered all of the possibilities with your various cases
and the default
cannot happen, this is the classic use case for assertions:
Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
// ...
break;
case BLUE:
// ...
break;
default:
assert false; // This cannot happen
// or:
throw new AssertionError("Invalid Colors enum");
}
Solution 6 - Java
To satisfy IDEs and other static linters, I often leave the default case in as a no-op, along with a comment such as // Can't happen
or // Unreachable
i.e., if the switch is doing the typical thing of handling all possible enum values, either explicitly or via fall-throughs, then the default case is probably programmer error.
Depending on the application, I sometimes put an assertion in the case to guard against programmer error during development. But this has limited value in shipping code (unless you ship with assertions enabled.)
Again, depending on the situation I might be convinced to throw an Error, as this is really an unrecoverable situation -- nothing the user can do will correct what is probably programmer error.
Solution 7 - Java
Yes, you should do it. You may change enum
but don't change switch
. In the future it'll lead to mistakes. I think that throw new IllegalArgumentException(msg)
is the good practice.
Solution 8 - Java
When the enum constants are too many and you need to handle only for few cases, then the default
will handle the rest of the constants.
Also, enum constants are references, if the reference is not yet set, or null
. You may have to handle such cases too.
Solution 9 - Java
Yes, it is dead code until someone add a value to the enum, which will make your switch statement follow the principle of 'fail fast' (https://en.wikipedia.org/wiki/Fail-fast)
This could relates to this question : https://stackoverflow.com/questions/16797529/how-to-ensure-completeness-in-an-enum-switch-at-compile-time
Solution 10 - Java
Apart from the possible future extending of the enum, which was pointed out by many, some day someone may 'improve' yout getColor()
or override it in a derived class and let it return an invalid value. Of course a compiler should catch that, unless someone explicitly forces unsafe type casting...
But bad things just happen, and it's a good practice not to leave any unexpected else
or default
path unguarded.
Solution 11 - Java
I'm surprised nobody else mentioned this. You can cast an int to an enum and it won't throw just because the value is not one of the enumerated values. This means (among other things), the compiler cannot tell that all the enum values are in the switch.
Even if you write your code correctly, this really does come up when serializing objects that contain enums. A future version might add to the enum and your code choke on reading it back, or somebody looking to create mayhem may hexedit a new value in. Either way, running off the switch rarely does the right thing. So, we throw in default unless we know better.
Solution 12 - Java
Here is how I would handle it, beside NULL
value which would result in a null pointer exception which you can handle.
If Color color
is not null
, it has to be one of the singletons in enum Color
, if you assign any reference to an object that is not one of the them this will cause a Runtime error.
So my solution is to account for values that are not supported.
Test.java
public Test
{
public static void main (String [] args)
{
try { test_1(null); }
catch (NullPointerException e) { System.out.println ("NullPointerException"); }
try { test_2(null); }
catch (Exception e) { System.out.println(e.getMessage()); }
try { test_1(Color.Green); }
catch (Exception e) { System.out.println(e.getMessage()); }
}
public static String test_1 (Color color) throws Exception
{
String out = "";
switch (color) // NullPointerException expected
{
case Color.Red:
out = Red.getName();
break;
case Color.Blue:
out = Red.getName();
break;
default:
throw new UnsupportedArgumentException ("unsupported color: " + color.getName());
}
return out;
}
.. or you can consider null
as unsupported too
public static String test_2 (Color color) throws Exception
{
if (color == null) throw new UnsupportedArgumentException ("unsupported color: NULL");
return test_1(color);
}
}
Color.java
enum Color
{
Red("Red"), Blue("Blue"), Green("Green");
private final String name;
private Color(String n) { name = n; }
public String getName() { return name; }
}
UnsupportedArgumentException.java
class UnsupportedArgumentException extends Exception
{
private String message = null;
public UnsupportedArgumentException() { super(); }
public UnsupportedArgumentException (String message)
{
super(message);
this.message = message;
}
public UnsupportedArgumentException (Throwable cause) { super(cause); }
@Override public String toString() { return message; }
@Override public String getMessage() { return message; }
}
Solution 13 - Java
In this case using Assertion in default is the best practice.