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