We’re trying to make switch
statements simpler to understand at a glance.
Misunderstanding the control flow of a switch
block is a common source of
bugs.
switch
statements:case
and the case’s code. For example, case
HEARTS:
case
switch
block is large, just skimming each case
can be toilsomecase
. When
conditionally falling-through multiple case
s in a row is possible, the
number of potential control flows can grow rapidlyswitch
statementscase
and the case’s code. For example, case
HEARTS ->
switch
statement, you know at a glance that no cases
fall through. No control flow analysis neededcase
s (within a switch
)case A, B, C
) for
improved readabilityenum 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 into the following expression 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");
}
}
}
Sometimes switch
is used with return
. Below, even though a case
is
specified for each possible value of the enum
, note that we nevertheless need
a “should never happen” clause:
enum SideOfCoin {OBVERSE, REVERSE};
private String foo(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");
}
Using an expression switch simplifies the code and removes the need for an explicit “should never happen” clause.
enum SideOfCoin {OBVERSE, REVERSE};
private String foo(SideOfCoin sideOfCoin) {
return switch(sideOfCoin) {
case OBVERSE -> "Heads";
case REVERSE -> "Tails";
};
}
If you nevertheless wish to have an explicit “should never happen” clause, this
can be accomplished by placing the logic under a default
case. For example:
enum SideOfCoin {OBVERSE, REVERSE};
private String foo(SideOfCoin sideOfCoin) {
return switch(sideOfCoin) {
case OBVERSE -> "Heads";
case REVERSE -> "Tails";
default -> {
// This should never happen
throw new RuntimeException("Unknown side of coin");
}
};
}
If every branch of a switch
is making an assignment to the same variable, it
can be re-written as an assignment 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;
};
}
Here’s an example of a complex statement switch
with conditional fall-through
and complex control flows. 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.