How does pattern matching for switch work in Java, and what does it change for you?
Pattern matching for switch lets you write case labels that test the type of the value, not just its value. It spent several releases as a preview feature before becoming final, and it is now what you should target if you want to use it in production code.
Before it existed, switching over a sealed hierarchy meant a chain of instanceof tests, and switching over a type with several implementations meant the old constant-style switch, which could only compare against constants. Now you can write case Circle c -> and the compiler binds c with the right type, and the switch is checked for exhaustiveness.
There are three shapes of label. A type pattern — case String s -> — matches when the value is of that type and binds it. A guarded pattern — case String s when s.length() > 3 -> — matches a type and an extra condition, and the guard is only evaluated when the type test succeeds. And a constant pattern — case 1 -> — is the original behaviour, now expressed with the same arrow syntax.
Two more capabilities come with it. case null is now allowed as an explicit branch, so you can handle a null reference in a switch instead of guarding before the switch. And case Type t -> where Type is a supertype acts as a catch-all for anything not matched earlier, which replaces the old default branch in many cases.
The sample above is the same dispatch written as a chain of instanceof tests, so you can run it as it is. With the arrow form those tests become cases in one switch: each branch gets its own typed binding, a guard attaches a condition to a single case instead of a separate if, and the compiler checks the whole switch for exhaustiveness.
public class Main {
public static void main(String[] args) {
System.out.println(describe("hello"));
System.out.println(describe(42));
System.out.println(describe(3.5));
System.out.println(describe(new int[]{1, 2, 3}));
}
// Type-based dispatch: one branch per type, checked in order.
static String describe(Object o) {
if (o instanceof String) {
return "a String of length " + ((String) o).length();
}
if (o instanceof Integer) {
return "an Integer holding " + o;
}
if (o instanceof Double) {
return "a Double holding " + o;
}
if (o instanceof int[]) {
return "an int array of length " + ((int[]) o).length;
}
return "something else";
}
}One detail is worth remembering for the interview. The order of the cases matters. A pattern for a supertype placed before a pattern for one of its subtypes makes the later branch unreachable, and the compiler rejects that as an error rather than a warning. That is dominance, and it is the rule that decides which case wins.