| We're trying to make long chains of `if` statements clearer (and potentially |
| faster) by converting them into `switch`es. |
| |
| ### Long chains of `if` statements |
| |
| * When a chain of `if ... else if ... else if ...` statements has `K` total |
| branches, at runtime one needs to check `O(K)` conditions on average |
| (assuming equal likelihood of each branch) |
| * Condition-checking expressions are often repeated multiple times, once in |
| each `if (...)`. Besides redundancy, this introduces a potential bug vector: |
| an `if` in the chain could unintentionally have a slightly different |
| condition than others, an ordering bug (see below), *etc.* |
| |
| ### `switch`es: |
| |
| * Support constants (`1`, `2`, ...), `enum` values, `null`, and pattern |
| matching, including mixtures of these |
| * Reduces redundancy |
| * Runtime performance may benefit |
| |
| ### Examples |
| |
| #### 1. Enum conversion |
| |
| ``` {.bad} |
| enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS}; |
| |
| private void foo(Suit suit) { |
| if (suit == Suit.SPADE) { |
| System.out.println("spade"); |
| } else if (suit == Suit.DIAMOND) { |
| System.out.println("diamond"); |
| } else if (suit == Suit.HEART) { |
| System.out.println("heart); |
| } else if (suit == Suit.CLUB) { |
| System.out.println("club"); |
| } |
| } |
| ``` |
| |
| Which can be converted into: |
| |
| ``` {.good} |
| enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS}; |
| |
| private void foo(Suit suit) { |
| switch (suit) { |
| case Suit.SPADE -> System.out.println("spade"); |
| case Suit.DIAMOND -> System.out.println("diamond"); |
| case Suit.HEART -> System.out.println("heart"); |
| case Suit.CLUB -> System.out.println("club"); |
| } |
| } |
| ``` |
| |
| If the flag `-XepOpt:IfChainToSwitch:EnableSafe=true` is set, the output will |
| include an empty `case null`; this more closely matches the behavior of the |
| original if-chain when `suit` is `null`, although is more verbose and may not |
| match the intent. |
| |
| ``` {.good} |
| enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS}; |
| |
| private void foo(Suit suit) { |
| switch (suit) { |
| case Suit.SPADE -> System.out.println("spade"); |
| case Suit.DIAMOND -> System.out.println("diamond"); |
| case Suit.HEART -> System.out.println("heart"); |
| case Suit.CLUB -> System.out.println("club"); |
| case null -> {} |
| } |
| } |
| ``` |
| |
| Note that with the new `switch` style (`->`), exhaustiveness checking is |
| provided by the compiler for 'enhanced' switch statements (see [JLS §14.11.2]), |
| and for traditional switch statements by Error Prone |
| (https://errorprone.info/bugpattern/MissingCasesInEnumSwitch). That is, if a new `Suit` value were to |
| be added to the `enum`, then the `switch` would raise a compile-time error, |
| whereas the original chain of `if` statements would need to be manually detected |
| and edited. |
| |
| [JLS §14.11.2]: https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.11.2 |
| |
| #### 2. Patterns |
| |
| This conversion works for `instanceof`s too: |
| |
| ``` {.bad} |
| enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS}; |
| |
| private void describeObject(Object obj) { |
| |
| if (obj instanceof String) { |
| System.out.println("It's a string!"); |
| } else if (obj instanceof Number n) { |
| System.out.println("It's a number!"); |
| } else if (obj instanceof Object) { |
| System.out.println("It's an object!"); |
| } |
| } |
| ``` |
| |
| This can be converted as follows (if the `instanceof` does not originally have a |
| pattern variable, then `unused` will be inserted): |
| |
| ``` {.good} |
| enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS}; |
| |
| private void describeObject(Object obj) { |
| |
| switch(obj) { |
| case String unused -> System.out.println("It's a string!"); |
| case Number n -> System.out.println("It's a number!"); |
| case Object unused -> System.out.println("It's an object!"); |
| } |
| } |
| ``` |
| |
| In later Java versions, an unnamed variable (`_`) can be used in place of |
| `unused`. |
| |
| #### 3. Ordering Bugs |
| |
| With `if` chains, it's possible to write code such as: |
| |
| ``` {.bad} |
| private void describeObject(Object obj) { |
| |
| if (obj instanceof Object) { |
| System.out.println("It's an object!"); |
| } else if (obj instanceof Number n) { |
| System.out.println("It's a number!"); |
| } else if (obj instanceof String) { |
| System.out.println("It's a string!"); |
| } |
| } |
| ``` |
| |
| When calling `describeObject("hello")`, one might expect to have `It's a |
| string!` printed, but this is not what happens. Because the `Object` check |
| happens first in code, it matches, resulting in `It's an object!`. This behavior |
| is most likely a bug, and can sometimes be hard to spot. This checker will |
| automatically reorder `case`s if needed to correct the issue, like this: |
| |
| ``` {.good} |
| private void describeObject(Object obj) { |
| |
| switch(obj) { |
| case Number n -> System.out.println("It's a number!"); |
| case String unused -> System.out.println("It's a string!"); |
| case Object unused -> System.out.println("It's an object!"); |
| } |
| } |
| ``` |
| |
| In this way, the behavior of the switch (`It's a string!`) and the original |
| if-chain (`It's an object!`) are different. To prevent the checker from changing |
| behavior in this way, set the flag `-XepOpt:IfChainToSwitch:EnableSafe=true`. |