We’re trying to make long chains of if statements clearer (and potentially
faster) by converting them into switches.
if statementsif ... 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)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.switches:1, 2, …), enum values, null, and pattern
matching, including mixtures of theseenum 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.
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.
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.
Suppress false positives by adding the suppression annotation @SuppressWarnings("IfChainToSwitch") to the enclosing element.