StatementSwitchToExpressionSwitch (original) (raw)
StatementSwitchToExpressionSwitch
This statement switch can be converted to a new-style arrow switch
The problem
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).
Old-style colon (:) switches:
- Have a colon between the
caseand thecase’s code. For example,case HEARTS: - Because of the potential for fall-through, it takes time and cognitive load to understand the control flow. When a
switchblock is large, just skimming eachcasecan 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 eachcase. When conditionally falling-through multiplecases, the number of potential control flows can grow rapidly - Lexical scopes overlap, which can lead to surprising behaviors: definitions of local variables from earlier
cases are propagated down to latercases, however the values that initialize those local variables do not propagate in the same way
New-style arrow (->) switches:
- Have an arrow between the
caseand thecase’s code. For example,case HEARTS -> - No
cases fall through; no control flow analysis needed - Safely and easily reorder
cases (within aswitch) - Lexical scopes are isolated between different
cases; if you define a local variable within acase, it can only be used within that specificcase.
Examples
1. Eliminate fall through
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");
}
}
}
2. 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).
3. Assignment switch
If 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;
};
...
}
4. Just converting to new arrow switch
Even 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);
}
}
}
5. Complex control flows
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;
}
Suppression
Suppress false positives by adding the suppression annotation @SuppressWarnings("StatementSwitchToExpressionSwitch") to the enclosing element.