Java: Switch Statement
Das Switch Statement hat in Java schon eine lange Tradition hinter sich. Bisher war die Ausdrucksweise etwas holprig, je nach Compiler und Restriktionen nicht wirklich handlich. Ab Java 12 wurde das Switch Statement schrittweise verbessert, mit Java 21 stehen nun vielseitige Möglichkeiten zur Verfügung, ganz nach dem Motto «Was lange währt, wird endlich gut»
Old Style
Wer kennt es nicht? Das bisherige Switch Statement erlaubt es, anstelle von «if … else if … else if …» etwas überschaubarer zu den verschiedenen Werten einer Variable entsprechende Logik zu implementieren. Dabei kann mit dem default-Wert ein Fallback implementiert werden für alle übrigen, nicht explizit getesteten Werte, analog zum abschliessenden «else …» im if-Statement.
String translate(String day) {
String tag;
switch (day) {
case "MONDAY":
tag = "Montag";
break;
case "TUESDAY":
tag = "Dienstag";
break;
...
case "SUNDAY":
tag = "Sonntag";
break;
default:
throw new IllegalArgumentException("invalid day " + day);
}
return tag;
}
Nebst der etwas unhandlichen Schreibweise verbergen sich eine Reihe von Problemen und Wünschen:
- Falls die Variable day in obigem Beispiel null ist, wird eine NullpointerException geworfen.
- Jeder Case muss mit einem break abgeschlossen werden, um aus dem Switch Statement herauszuspringen. Alternativ kann der Case als «Einstiegspunkt» gewählt werden, um diesen und die nachfolgenden Cases zu durchlaufen (bis zum nächsten break). Je nach Compiler und Restriktionen muss allenfalls mit einem $FALLTHROUGH-Kommentar «nachgeholfen» und die Beabsichtigung dokumentiert werden.
- Als Switch Variable können nur die primitiven Datentype byte, short, int und char und deren Wrapper-Klassen sowie enum und String als Konstanten verwendet werden. Insbesondere können keine (logischen) Ausdrücke oder Objekte verwendet werden.
- Beim Typ enum kann vom Compiler nicht überprüft werden, ob alle Werte als Case vorkommen. Dasselbe gilt für Sealed Classes, welche mit Java 17 eingeführt worden sind.
Java 21
Mit Java 17 stehen schon eine ganze Reihe von Verbesserungen zur Verfügung. Es lohnt sich, bereits diese genauer anzuschauen und einzusetzen. Mit Java 21 kommt das Switch Statement (endlich!) in einer vielseitigen und praktischen Version daher. Die wichtigsten Punkte sind:
- Pattern matching als Case Variable (im Zusammenhang mit der instanceof-Erweiterung)
- Verarbeiten von null Values (ohne NullpointerException!)
- Überprüfung auf Vollständigkeit von Enums und Sealed Classes
- Unterstützung von beliebigen, auch gemischten Objekten als Switch Variable.
Damit ist es möglich, eine Vielzahl von Verbesserungen und Vereinfachungen zu implementieren, womit die verschiedenen Cases klarer ausgedrückt werden können. Das vollständige Feature ist in JEP 441 dokumentiert.
Beispiel 1
boolean isWeekend(String day) {
return switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> false;
case "SATURDAY", "SUNDAY" -> true;
case null -> throw new IllegalArgumentException("undefined day");
default -> throw new IllegalArgumentException("invalid day " + day);
};
}
Die Aufzählung von mehreren Werten und demselben Resultat erhöhen die Lesbarkeit sehr, zudem sind Null Values und übrige / Default Values klar definiert. Falls anstelle der Strings „MONDAY“, „TUESDAY“, „WEDNESDAY“, etc. Enums verwendet würden, könnte der Compiler auf Vollständigkeit prüfen und es wäre kein Default Statement notwendig; der Compiler würde sonst «the switch expression does not cover all possible input values» melden.
String translate(Weekdays day) {
return switch (day) {
// all possible input values are checked by the compiler
case MONDAY -> "Montag";
case TUESDAY -> "Dienstag";
case WEDNESDAY -> "Mittwoch";
case THURSDAY -> "Donnerstag";
case FRIDAY -> "Freitag";
case SATURDAY -> "Samstag";
case SUNDAY -> "Sonntag";
case null -> throw new IllegalArgumentException("undefined day");
// no default case is necessary
};
}
Das Einstiegsbeispiel aus dem Abschnitt «Old Style», implementiert mit Enum Weekdays – kompakt, übersichtlich und unterstützt vom Compiler!
Beispiel 2
void typeTester(Object obj) {
switch (obj) {
case null -> System.out.println("null");
case String s -> System.out.println("String of length " + s.length());
case Color c -> System.out.println("Color class: " + c);
case Point p -> System.out.println("Record class: " + p);
case int[] ia -> System.out.println("int array of length " + ia.length);
default -> System.out.println("Something else " + obj);
}
}
Mit dem ersten Case wird die Methode «null safe», danach werden spezifische Typen als Case definiert (analog zu instanceof). Das Default Statement verarbeitet alle übrigen Werte bzw. Klassen.
Beispiel 3
void recordInferenceJdk21(MyPair pair) {
switch (pair) {
case MyPair(var text, var count) when text.contains("Michael") ->
System.out.println(text + " is " + count + " years old");
case MyPair(var text, var count) when count > 5 && count < 10 ->
System.out.println("repeated " + text.repeat(count));
case MyPair(var text, var count) -> System.out.println(text + count);
default -> System.out.println("NOT HANDLED");
}
}
Das Paar (Pair), bestehend aus String (text) und Integer (count), wird aufgrund der einzelnen Attribute unterschiedlich verarbeitet: Enthält der Text «Michael», dann wird der Text als (Vor-)Name und der Count als Alter interpretiert. Falls der Count zwischen 5 und 10 liegt, wird der Text n=count mal wiederholt ausgegeben. Alle übrigen Paare werden als Text und Count ausgegeben. Der Default wird nicht durchlaufen, da der dritte Case bereits der Fallback für alle übrigen Paare ist.
Fazit
Mit Java 21 stehen nun eine Vielzahl von Möglichkeiten im Switch Statement zur Verfügung. Die früheren Einschränkungen sind ausgemerzt und der Compiler kann im Fall von Enums und Sealed Classes auf Vollständigkeit der Cases prüfen.
Ganz nach dem Motto «Was lange währt, wird endlich gut»!
Quellen und weitere Informationen
- JEP 441: Pattern Matching for switch: https://openjdk.org/jeps/441
- Michael Inden: Best of Modern Java 21-22: https://github.com/Michaeli71/Best-Of-Modern-Java-21-22-My-Favorite-Features
- Testbeispiele: https://gitlab.puzzle.ch/public-blog-examples/java/java-features