IfChainToSwitch
This if-chain may be converted into a switch

Severity
WARNING

The problem

We’re trying to make long chains of if statements clearer (and potentially faster) by converting them into switches.

Long chains of if statements

switches:

Examples

1. Enum conversion

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:

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.

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.

2. Patterns

This conversion works for instanceofs too:

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):

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:

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 cases if needed to correct the issue, like this:

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.

Suppression

Suppress false positives by adding the suppression annotation @SuppressWarnings("IfChainToSwitch") to the enclosing element.