Including a default case is redundant when switching on an enum type if the
switch handles all possible values of the enum, and execution cannot fall
through from a case
into the default
.
Note: This check does not apply to pseudo-enums such as Android @IntDef
s,
which are integers that are treated specially by other tools.
TIP: Removing the unnecessary default allows Error Prone to enforce that the switch continues to handle all cases, even if new values are added to the enum, see: MissingCasesInEnumSwitch. After the unnecessary default is removed, Error Prone will report an error if new enum constants are added in the future, to remind you to either handle the cases explicitly or restore the default case.
This check does not report cases where execution can continue after the switch
statement from any non-default statement groups, and removing the default
would prevent the code from compiling. For example, consider:
enum TrafficLightColour { RED, GREEN, YELLOW }
void approachIntersection(TrafficLightColour state) {
boolean stop;
switch (state) {
case GREEN:
stop = false;
break;
case YELLOW:
case RED:
stop = true;
break;
}
if (stop) {
...
}
}
The definition of control flow in JLS §14.21 does not consider whether enum
switches handle all cases, so in the example above javac will complain that
stop
is not definitely assigned. This is because adding constants to an enum
is a binary compatible change (see JLS §13.4.26), so the spec allows for the
possibility that TrafficLightColour
is defined in another library, and after
compiling our code we update to a new version of the library (without
recompiling) that adds another colour of traffic light (say, PURPLE
) that the
switch doesn’t handle.
This check should be used together with MissingCasesInEnumSwitch
in
environments where that kind of binary incompatibility is very unlikely. For
example, if your build system accurately tracks changes to dependencies and you
are deploying an application (instead of a library), the risk of skew between
compile-time and runtime is minimal. On the other hand, if you are a library
author and your code switches on an enum in a different library, you want to
include ‘defensive’ default cases to handle the situation where a user deploys
your code together with an incompatible version of the other library.
The following examples show situations where it is usually safe to remove the default from an exhaustive enum switch.
Before:
enum State { ON, OFF }
boolean isOn(State state) {
switch (state) {
case ON:
return true;
case OFF:
return false;
default:
throw new AssertionError(state);
}
}
After:
enum State { ON, OFF }
boolean isOn(State state) {
switch (state) {
case ON:
return true;
case OFF:
return false;
}
throw new AssertionError(state);
}
Before:
enum State { ON, OFF }
boolean isOn(State state) {
switch (state) {
case ON:
return true;
case OFF:
break;
default:
break;
}
return false;
}
After:
enum State { ON, OFF }
boolean isOn(State state) {
switch (state) {
case ON:
return true;
case OFF:
break;
}
return false;
}
proto3 enums implicitly add an UNRECOGNIZED value to all enums. If a switch
statement handles all values of a proto-generated enum except for UNRECOGNIZED,
and has a default cause, we assume this is an attempt to exhaustively cover all
cases. But in the future, if a new enum value is added, that case will be
silently caught up in the default case. To avoid this, remove the default case
and handle UNRECOGNIZED explicitly. This way, MissingCasesInEnumSwitch
will
catch unexpected enum types at compile-time instead of runtime.
If the switch statement cannot complete normally, the default should be deleted and its statements moved after the switch statement. The UNRECOGNIZED case should be added with a break.
If it can complete normally, the default should be merged with an added UNRECOGNIZED case.
Suppress false positives by adding the suppression annotation @SuppressWarnings("UnnecessaryDefaultInEnumSwitch")
to the enclosing element.