We’re trying to make switches simpler to understand at a glance.
Misunderstanding the control flow of a switch is a common source of bugs.
As part of this simplification, new-style arrow (->) switches are encouraged
instead of old-style colon (:) switches. And where possible, neighboring cases
are grouped together (e.g. case A, B, C).
:) switches:case and the case’s code. For example, case
HEARTS:switch block is large, just
skimming each case can be toilsome. Fall-through can also be conditional
(see example 5. below). In this scenario, one would potentially need to
reason about all possible flows for each case. When conditionally
falling-through multiple cases, the number of potential control flows can
grow rapidlycases are propagated down to later
cases, however the values that initialize those local variables do not
propagate in the same way->) switches:case and the case’s code. For example, case
HEARTS ->cases fall through; no control flow analysis neededcases (within a switch)cases; if you define a local
variable within a case, it can only be used within that specific case.enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void foo(Suit suit) {
switch(suit) {
case HEARTS:
System.out.println("Red hearts");
break;
case DIAMONDS:
System.out.println("Red diamonds");
break;
case SPADES:
// Fall through
case CLUBS:
bar();
System.out.println("Black suit");
}
}
Which can be simplified by grouping and using a new-style switch:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void foo(Suit suit) {
switch(suit) {
case HEARTS -> System.out.println("Red hearts");
case DIAMONDS -> System.out.println("Red diamonds");
case SPADES, CLUBS -> {
bar();
System.out.println("Black suit");
}
}
}
return switch ...Sometimes switch is used with a return for each case, like this:
enum SideOfCoin {OBVERSE, REVERSE};
private String renderName(SideOfCoin sideOfCoin) {
switch(sideOfCoin) {
case OBVERSE:
return "Heads";
case REVERSE:
return "Tails";
}
// This should never happen, but removing this will cause a compile-time error
throw new RuntimeException("Unknown side of coin");
}
Note that even though a case is present for each possible value of the enum,
a boilerplate “should never happen” clause is still needed. The transformed code
is simpler and doesn’t need a “should never happen” clause.
enum SideOfCoin {OBVERSE, REVERSE};
private String renderName(SideOfCoin sideOfCoin) {
return switch(sideOfCoin) {
case OBVERSE -> "Heads";
case REVERSE -> "Tails";
};
}
If you nevertheless wish to define an explicit “should never happen” clause,
this can be accomplished by placing the logic inside a default case. For
example:
enum SideOfCoin {OBVERSE, REVERSE};
private String foo(SideOfCoin sideOfCoin) {
return switch(sideOfCoin) {
case OBVERSE -> "Heads";
case REVERSE -> "Tails";
default -> throw new RuntimeException("Unknown side of coin"); // should never happen
};
}
When the checker detects an existing default that appears to be redundant, it
may suggest a secondary auto-fix which removes the redundant default and its
code (if any).
switchIf every branch of a switch is making an assignment to the same variable, the
code can be simplified into a combined assignment and switch:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
int score = 0;
private void updateScore(Suit suit) {
switch(suit) {
case HEARTS:
// Fall thru
case DIAMONDS:
score += -1;
break;
case SPADES:
score += 2;
break;
case CLUBS:
score += 3;
}
}
This can be simplified as follows:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
int score = 0;
private void updateScore(Suit suit) {
score += switch(suit) {
case HEARTS, DIAMONDS -> -1;
case SPADES -> 2;
case CLUBS -> 3;
};
}
Taking this one step further: if a local variable is defined, and then
immediately followed by a switch in which every case assigns to that same
variable, then all three (the switch, the variable declaration, and the
assignment) can be merged:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void updateStatus(Suit suit) {
int score;
switch(suit) {
case HEARTS:
// Fall thru
case DIAMONDS:
score = 1;
break;
case SPADES:
score = 2;
break;
case CLUBS:
score = 3;
}
...
}
Becomes:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void updateStatus(Suit suit) {
int score = switch(suit) {
case HEARTS, DIAMONDS -> 1;
case SPADES -> 2;
case CLUBS -> 3;
};
...
}
switchEven when the simplifications discussed above are not applicable, conversion to
new arrow switches can be automated by this checker:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void processEvent(Suit suit) {
switch (suit) {
case CLUBS:
String message = "hello";
var anotherMessage = "salut";
processMessages(message, anotherMessage);
break;
case DIAMONDS:
anotherMessage = "bonjour";
processMessage(anotherMessage);
}
}
Note that the local variables referenced in multiple cases are hoisted up out of
the switch statement, and var declarations are converted to explicit types,
resulting in:
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private void processEvent(Suit suit) {
String anotherMessage;
switch (suit) {
case CLUBS -> {
String message = "hello";
anotherMessage = "salut";
processMessages(message, anotherMessage);
}
case DIAMONDS -> {
anotherMessage = "bonjour";
processMessage(anotherMessage);
}
}
}
Here’s an example of a complex statement switch with conditional fall-through
and various control flows. Unfortunately, the checker does not currently have
the ability to automatically convert such code to new-style arrow switches.
Manually converting the code could be a good opportunity to improve its
readability.
How many potential execution paths can you spot?
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
private int foo(Suit suit){
switch(suit) {
case HEARTS:
if (bar()) {
break;
}
// Fall through
case CLUBS:
if (baz()) {
return 1;
} else if (baz2()) {
throw new AssertionError(...);
}
// Fall through
case SPADES:
// Fall through
case DIAMONDS:
return 0;
}
return -1;
}
Suppress false positives by adding the suppression annotation @SuppressWarnings("StatementSwitchToExpressionSwitch") to the enclosing element.