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; }