1. Klassischer switch: Beschränkungen der Vergangenheit
Erinnern wir uns, wie der switch in Java vor dem Pattern Matching war. Ehrlich gesagt ziemlich konservativ. Er arbeitete nur mit primitiven Typen (int, char und etwas später enum, String), und wenn Sie unterschiedliche Objekttypen verarbeiten wollten, mussten Sie lange if-else-Ketten schreiben.
Object obj = ...;
if (obj instanceof String) {
String s = (String) obj;
System.out.println("String: " + s);
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
System.out.println("Zahl: " + i);
} else if (obj == null) {
System.out.println("null!");
} else {
System.out.println("Etwas anderes");
}
Bequem? Nicht wirklich. Der Code ist sperrig, Fehler passieren leicht, und wenn ein neuer Typ hinzukommt, müssen Sie die if-else-Kette wieder umschreiben.
2. Pattern Matching in switch: eine Revolution in Java 17/21+
Java 17 (Preview) und endgültig in Java 21+ haben eine neue Version von switch eingeführt, die Pattern Matching unterstützt. Jetzt kann switch nicht nur mit Primitiven, sondern auch mit beliebigen Objekten und Typen arbeiten, sogar mit null, und unterstützt zusätzliche Bedingungen (Guard-Ausdrücke).
Neue Syntax: knapp und mächtig
Beispiel für einfaches Pattern Matching in switch:
Object obj = ...;
switch (obj) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Zahl: " + i);
case null -> System.out.println("null!");
default -> System.out.println("Etwas anderes");
}
Was passiert hier?
- Wenn obj ein String ist, wird die Variable s automatisch auf den Typ String gecastet.
- Wenn obj eine Ganzzahl ist, wird die Variable i zu Integer.
- Wenn obj null ist, greift ein spezieller Zweig.
- Alles andere — default.
Warum ist das praktisch?
- Kein manuelles Casting: Java kümmert sich selbst um die Typsicherheit.
- Weniger Code: Keine überflüssigen Klammern, Casts und Duplikate.
- Sicherer: Der Compiler prüft, dass Sie mögliche Varianten nicht vergessen (insbesondere bei sealed-Klassen, auf die wir später eingehen).
- Neue Typen lassen sich leicht hinzufügen: Fügen Sie einfach einen weiteren case-Zweig hinzu.
3. Pattern Matching in switch: grundlegende Beispiele
Beispiel 1: Unterschiedliche Typen verarbeiten
Object obj = 42;
switch (obj) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Zahl: " + i);
case null -> System.out.println("Das ist null");
default -> System.out.println("Unbekannter Typ");
}
Ausgabe:
Zahl: 42
Beispiel 2: Guard-Ausdrücke (when) verwenden
Manchmal reicht der Typ allein nicht – man möchte eine zusätzliche Bedingung hinzufügen.
Object obj = "Hello, world!";
switch (obj) {
case String s when s.length() > 10 -> System.out.println("Langer String: " + s);
case String s -> System.out.println("Kurzer String: " + s);
case Integer i when i > 0 -> System.out.println("Positive Zahl: " + i);
case Integer i -> System.out.println("Nichtpositive Zahl: " + i);
default -> System.out.println("Etwas anderes");
}
Ausgabe:
Langer String: Hello, world!
Beachten Sie:
- when ist ein Guard-Ausdruck, eine zusätzliche Prüfung für case.
- Wenn die Bedingung nicht erfüllt ist, werden die nächsten case geprüft.
Beispiel 3: null behandeln
Object obj = null;
switch (obj) {
case null -> System.out.println("Das ist null!");
default -> System.out.println("Nicht null");
}
Ausgabe:
Das ist null!
4. Pattern Matching in switch mit Klassen und Vererbung
Pattern Matching in switch funktioniert besonders gut mit Klassenhierarchien.
sealed interface Shape permits Circle, Rectangle, Square {}
final class Circle implements Shape {
final double radius;
Circle(double radius) { this.radius = radius; }
}
final class Rectangle implements Shape {
final double width, height;
Rectangle(double width, double height) { this.width = width; this.height = height; }
}
final class Square implements Shape {
final double side;
Square(double side) { this.side = side; }
}
Shape shape = new Rectangle(2, 3);
String description = switch (shape) {
case Circle c -> "Kreis mit Radius " + c.radius;
case Rectangle r -> "Rechteck " + r.width + "x" + r.height;
case Square s -> "Quadrat mit Seitenlänge " + s.side;
};
System.out.println(description);
Ausgabe:
Rechteck 2.0x3.0
Das Besondere: Wenn Sie sealed-Klassen verwenden und alle ihre Varianten behandeln, ist der default-Zweig nicht erforderlich – der Compiler versteht, dass es keine weiteren Varianten geben kann!
5. switch mit record-Klassen und verschachtelten Mustern verwenden
Pattern Matching in switch unterstützt die Arbeit mit record-Klassen und verschachtelten Mustern (ab Java 21+).
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Line(new Point(1, 2), new Point(3, 4));
String result = switch (obj) {
case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
"Linie von (" + x1 + "," + y1 + ") bis (" + x2 + "," + y2 + ")";
case Point(int x, int y) -> "Punkt (" + x + "," + y + ")";
default -> "Unbekanntes Objekt";
};
System.out.println(result);
Ausgabe:
Linie von (1,2) bis (3,4)
6. Besonderheiten und Einschränkungen des Pattern Matchings in switch
Variablen sind nur innerhalb des case sichtbar
Eine im Muster deklarierte Variable (zum Beispiel String s) existiert nur innerhalb des entsprechenden case-Blocks. Außerhalb ist sie nicht verfügbar.
switch (obj) {
case String s -> System.out.println(s); // hier ist s sichtbar
// System.out.println(s); // hier Kompilierfehler!
}
Behandlung von null
- Im alten switch über Objekten führte der Versuch, null zu übergeben, zu einer NullPointerException.
- Im neuen Pattern-Matching-switch kann null explizit über case null behandelt werden.
Guard-Ausdrücke (when)
Zusätzliche Bedingungen (when) sind nur im expression-style-switch verfügbar (Pfeilsyntax ->).
case String s when s.isEmpty() -> System.out.println("Leerer String");
Anforderungen an JDK und IDE
- Pattern Matching in switch ist eine Funktion von Java 17 (Preview) und Java 21+ (stabil).
- Für den Betrieb werden JDK 21 und eine IDE benötigt, die die neuen Möglichkeiten unterstützt (z. B. IntelliJ IDEA 2023+).
7. Einsatz des Pattern-Matching-switch in einer realen Anwendung
Betrachten wir eine Übungsanwendung – einen einfachen Task-Tracker. Angenommen, Sie haben einen abstrakten Aufgabentyp und verschiedene Ausprägungen:
sealed interface Task permits SimpleTask, DeadlineTask, MeetingTask {}
final class SimpleTask implements Task {
final String description;
SimpleTask(String description) { this.description = description; }
}
final class DeadlineTask implements Task {
final String description;
final java.time.LocalDate deadline;
DeadlineTask(String description, java.time.LocalDate deadline) {
this.description = description;
this.deadline = deadline;
}
}
final class MeetingTask implements Task {
final String topic;
final java.time.LocalTime time;
MeetingTask(String topic, java.time.LocalTime time) {
this.topic = topic;
this.time = time;
}
}
Die gesamte Logik in einem kompakten switch:
Task task = new DeadlineTask("Projekt abgeben", java.time.LocalDate.of(2024, 7, 1));
String info = switch (task) {
case SimpleTask st -> "Einfache Aufgabe: " + st.description;
case DeadlineTask dt -> "Aufgabe mit Deadline: " + dt.description + ", bis " + dt.deadline;
case MeetingTask mt -> "Meeting: " + mt.topic + " um " + mt.time;
};
System.out.println(info);
Ausgabe:
Aufgabe mit Deadline: Projekt abgeben, bis 2024-07-01
8. Häufige Fehler bei der Verwendung von Pattern Matching in switch
Fehler Nr. 1: null nicht behandelt. Wenn Sie case null nicht hinzufügen und obj null sein kann – erhalten Sie eine MatchException (oder in alten switch eine NullPointerException). Behandeln Sie null nach Möglichkeit immer explizit.
Fehler Nr. 2: Nicht alle Untertypen einer sealed-Klasse berücksichtigt. Wenn Sie eine sealed-Klasse verwenden und nicht alle Varianten im switch behandeln, meldet der Compiler einen Fehler oder verlangt einen default-Zweig. Vergessen Sie nicht, beim Erweitern der Hierarchie neue case-Zweige hinzuzufügen.
Fehler Nr. 3: Pattern-Variable außerhalb des case-Blocks nicht verfügbar. Eine im Muster deklarierte Variable existiert nur innerhalb des Pfeil-Blocks. Der Versuch, sie außerhalb zu verwenden, führt zu einem Kompilierfehler.
Fehler Nr. 4: Neue Features auf alter JDK oder in alter IDE. Pattern Matching in switch erfordert Java 21+ und entsprechende Unterstützung in Ihrer IDE. Wenn Sie eine alte Version verwenden, erhalten Sie einen Kompilierfehler oder unzutreffende Hinweise.
Fehler Nr. 5: Verwechslung bei Guard-Ausdrücken. Versuchen Sie, when im alten blockbasierten switch zu verwenden? Das geht nicht. Guards sind nur für die Pfeilsyntax -> verfügbar.
Fehler Nr. 6: default-Zweig bei offenen Hierarchien vergessen. Wenn Ihr switch nicht nur mit sealed-Klassen arbeitet, fügen Sie unbedingt default hinzu – sonst werden nicht alle Varianten abgedeckt.
GO TO FULL VERSION