switch
Two forms now: the old one with break, and the arrow one without.
The classic form
switch (day) {
case 1:
System.out.println("Monday");
break;
case 6:
case 7:
System.out.println("Weekend");
break;
default:
System.out.println("Midweek");
}
Execution jumps to the matching label and continues until a break. That is fall-through, and it is why two labels stacked together work as an “or”.
It is also the classic bug. Omit a break and the next case’s body runs too, silently. Most of the switch statements in the wild that misbehave misbehave for this reason.
The arrow form
switch (day) {
case 1 -> System.out.println("Monday");
case 6, 7 -> System.out.println("Weekend");
default -> System.out.println("Midweek");
}
No fall-through, no break, several labels on one line. Prefer this. It removes the failure mode rather than asking you to remember.
switch as an expression
The arrow form can produce a value:
String name = switch (day) {
case 1 -> "Monday";
case 6, 7 -> "Weekend";
default -> "Midweek";
};
Note the semicolon — it is an expression, so the statement needs terminating.
As an expression, switch must be exhaustive: every possible value covered, or a default. The compiler enforces it, which is genuinely useful with enums, because adding a constant then breaks the build at every switch that has not been updated rather than silently taking the default.
For a multi-line branch use a block and yield:
case 6, 7 -> {
log("weekend request");
yield "Weekend";
}
What can be switched on
int and the smaller integral types, char, String, and enums. Not long, float or double.
With enums, omit the type name in the labels:
switch (status) {
case ACTIVE -> ...;
case CLOSED -> ...;
}
When not to use it
A switch over a type, where each branch does something the object could do itself, is usually a missed polymorphism. If adding a new case means editing several switches in different files, that is the signal.