| We're trying to make `switch`es 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 (`:`) `switch`es: | |
| * Have a colon between the `case` and the `case`'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 `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 `case`s, the number of potential control flows can | |
| grow rapidly | |
| * Lexical scopes overlap, which can lead to surprising behaviors: definitions | |
| of local variables from earlier `case`s are propagated down to later | |
| `case`s, however the *values* that initialize those local variables do not | |
| propagate in the same way | |
| ### New-style arrow (`->`) `switch`es: | |
| * Have an arrow between the `case` and the `case`'s code. For example, `case | |
| HEARTS ->` | |
| * No `case`s fall through; no control flow analysis needed | |
| * Safely and easily reorder `case`s (within a `switch`) | |
| * Lexical scopes are isolated between different `case`s; if you define a local | |
| variable within a `case`, it can only be used within that specific `case`. | |
| ### Examples | |
| #### 1. Eliminate fall through | |
| ``` {.bad} | |
| 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: | |
| ``` {.good} | |
| 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: | |
| ``` {.bad} | |
| 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`: | |
| ``` {.bad} | |
| 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: | |
| ``` {.bad} | |
| 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 `switch`es can be automated by this checker: | |
| ``` {.bad} | |
| 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 `switch`es. | |
| Manually converting the code could be a good opportunity to improve its | |
| readability. | |
| How many potential execution paths can you spot? | |
| ``` {.bad} | |
| 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; | |
| } | |
| ``` |