Moderne Java Switch-Ausdrücke
Moderne Switch-Ausdrücke in Java mit Pfeil-Labels, yield, Vollständigkeit und Pattern Matching einsetzen.
Das klassische switch-Statement ist seit Version 1.0 Bestandteil von Java, brachte aber viel Gepäck mit: Fall-Through-Bugs, repetitive break-Anweisungen und keine Möglichkeit, einen Wert zu erzeugen. Switch-Ausdrücke, die in Java 14 finalisiert wurden, beheben all das. Sie verwandeln switch von einem umständlichen Kontrollfluss-Statement in einen prägnanten, wertproduzierenden Ausdruck.
Dieses Kapitel führt durch das moderne switch: Pfeil-Labels, das Schlüsselwort yield, Multi-Label-Cases, Vollständigkeitsprüfung und genau den Unterschied zwischen der neuen Form und dem Statement, das Sie möglicherweise bereits kennen.
Vom Statement zum Ausdruck
Das klassische switch-Statement führt Nebeneffekte aus und verlässt sich auf break, um Fall-Through zu stoppen. Vergisst man ein break, fällt die Ausführung stillschweigend in den nächsten Case – eine bekannte Fehlerquelle.
// Traditional switch statement (error-prone)
String kind;
switch (day) {
case SATURDAY:
case SUNDAY:
kind = "weekend";
break; // forget this and you fall through
default:
kind = "weekday";
}Ein Switch-Ausdruck reduziert das auf eine einzige Zuweisung. Die Pfeil-Form (->) fällt nie durch, daher wird kein break benötigt.
// Modern switch expression
String kind = switch (day) {
case SATURDAY, SUNDAY -> "weekend";
default -> "weekday";
};Das gesamte switch wertet nun zu einem Wert aus, den Sie direkt zuweisen, zurückgeben oder als Argument übergeben können.
Pfeil-Labels und Multi-Label-Cases
Das Pfeil-Label case L -> verknüpft ein Label (oder mehrere, kommagetrennt) mit einer einzelnen Aktion. Nur der passende Zweig wird ausgeführt – es gibt kein Fall-Through, um das man sich sorgen müsste.
int numLetters = switch (month) {
case JANUARY, JUNE, JULY -> 4;
case FEBRUARY, MARCH, APRIL, MAY -> 5;
case SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> switchOnLength(month);
case AUGUST -> 6;
};Das Gruppieren von Labels mit Kommas ersetzt den alten Trick, leere case-Zeilen zu stapeln, um einen Body zu teilen, und ist deutlich lesbarer.
| Merkmal | Traditionelles switch (case L:) | Modernes switch (case L ->) |
|---|---|---|
| Fall-Through | Ja, außer mit break | Nein, jeder Zweig ist isoliert |
| Erzeugt einen Wert | Nein | Ja (es ist ein Ausdruck) |
| Mehrere Labels | Gestapelte leere case-Zeilen | Kommagetrennt in einer Zeile |
| Geltungsbereich von Variablen | Geteilt im gesamten Block | Lokal für jeden Zweig-Block |
Blöcke und das yield-Schlüsselwort
Wenn ein Zweig mehr als einen einzelnen Ausdruck benötigt, verwendet man einen Block { ... } und gibt seinen Wert mit yield zurück. Das Schlüsselwort yield verhält sich zu einem Switch-Ausdruck wie return zu einer Methode: Es liefert den Wert, den der Zweig erzeugt.
int gradePoints = switch (grade) {
case 'A' -> 4;
case 'B' -> 3;
default -> {
log("Unknown grade: " + grade);
yield 0; // the value this branch evaluates to
}
};Man kann Doppelpunkt-Labels weiterhin mit yield verwenden, wenn man die alte Syntax bevorzugt, aber die Pfeil-Form ist die idiomatisch moderne Wahl und vermeidet versehentliches Fall-Through vollständig.
Vollständigkeit und default
Ein Switch-Ausdruck muss vollständig sein: Jede mögliche Eingabe muss behandelt werden, weil der Ausdruck unabhängig vom Eingabewert einen Wert erzeugen muss. Bei den meisten Typen erfüllt man das mit einem default-Zweig. Bei einem enum kann der Compiler die Vollständigkeit direkt prüfen – wenn alle Konstanten abgedeckt sind, wird default optional.
// No default needed: all enum constants are covered,
// so the compiler knows the switch is exhaustive.
boolean isWeekend = switch (day) {
case SATURDAY, SUNDAY -> true;
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
};Lässt man eine Konstante weg und es gibt kein default, kompiliert der Code nicht. Dieses Sicherheitsnetz zur Compile-Zeit ist einer der größten praktischen Vorteile gegenüber dem alten Statement, das eine Variable stillschweigend unzugewiesen lassen würde.
Pattern Matching in switch
Neuere Java-Versionen erweitern switch, sodass jeder case auf den Typ des Werts statt nur auf eine Konstante matchen kann. Das ist Pattern Matching für switch (als Preview in Java 17–20, finalisiert in Java 21). Anstatt eine Kette von instanceof-Prüfungen und Casts zu schreiben, versieht man jeden Zweig mit einem Typmuster und bindet dabei eine Variable in einem Schritt.
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "int " + i; // matches and binds i
case String s -> "string of length " + s.length();
case null -> "nothing"; // null can be its own label
default -> "something else";
};
}Pattern Matching ist besonders leistungsfähig mit records und Sealed Types: Wenn ein switch jeden erlaubten Subtyp eines Sealed Types abdeckt, behandelt der Compiler ihn als vollständig, sodass man default ganz weglassen kann.
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
static double area(Shape shape) {
return switch (shape) { // no default: all permitted types covered
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
};
}switch nur ein enum, einen ganzzahligen Typ oder einen String testen. Typmuster ermöglichen es switch, mit beliebigen Referenztypen zu arbeiten, was es zu einem natürlichen Ersatz für lange if/else if-Ketten macht.Ein vollständiges Arbeitsbeispiel
Das folgende Programm fügt alle Teile zusammen: Pfeil-Labels mit Multi-Label-Cases, ein yield-Block für einen berechneten Zweig, vollständige Enum-Abdeckung und ein Switch-Ausdruck, der direkt einer Variablen zugewiesen wird.
Was man aus dem Lauf mitnimmt:
kind()gibt das Ergebnis eines Switch-Ausdrucks direkt zurück –MONDAYundFRIDAYgebenweekdayaus,SATURDAYgibtweekendaus, alles ohne ein einzigesbreak.- Die beiden Pfeil-Zweige in
kind()decken alle sieben Enum-Konstanten ab, sodass der Switch vollständig ist und keindefaultbenötigt. - In
letterGrade()werden die Noten 95, 83, 71 und 64 sauber über Pfeil-Labels auf 4, 3, 2 und 1 Notenpunkte abgebildet. - Die Note 42 trifft den
default-Block, der zuerst(failing score 42)über die Nebeneffekt-Zeile ausgibt und dannyields 0 – was zeigt, wie ein Block-Zweig Arbeit leisten kann, bevor er seinen Wert erzeugt. - Das letzte
switchweistTWOdirekt der Variablenlabelzu und beweist, dass ein Switch-Ausdruck ein speicherbarer Wert ist, nicht nur Kontrollfluss.
Wann welche Form verwenden
- Greifen Sie zu einem Switch-Ausdruck (
case L ->), wann immer das Ziel darin besteht, einen einzelnen Wert zu berechnen und zurückzugeben. Er ist vollständig, frei von Fall-Through und liest sich wie eine einzige Zuweisung. - Ein traditionelles switch-Statement ist noch sinnvoll, wenn jeder Zweig rein aus einem Nebeneffekt besteht (Logging, Dispatching) und kein Wert zu erzeugen ist.
- Verwenden Sie Typmuster, wenn Sie auf den Laufzeittyp eines Objekts verzweigen, insbesondere über die Konstanten eines
enumoder die Subtypen eines Sealed Types.
Für die vollständige Entstehungsgeschichte und die Doppelpunkt-Label-Form, siehe Java switch expressions.