W3docs

Java Pattern Matching

Pattern Matching in Java für instanceof und switch – Typmuster, Record-Muster und Destrukturierung erklärt.

Jahrelang folgte Java-Code, der mit Werten unbekannten Typs arbeitete, einem mühsamen Ritual: den Typ mit instanceof prüfen, dann zu diesem Typ casten und dann verwenden. Pattern Matching fasst dieses Ritual in einem einzigen Ausdruck zusammen. Ein Muster beschreibt die Form der Daten; wenn ein Wert übereinstimmt, bindet Java seine Teile an Variablen, die sofort verwendet werden können – kein manueller Cast erforderlich.

Pattern Matching kam schrittweise: zuerst instanceof-Muster, dann Muster in switch, dann Record-Muster, die Records in ihre Komponenten zerlegen. Zusammen ermöglichen sie das Schreiben von deklarativem, typsicherem Code, der wie die Daten aussieht, mit denen er arbeitet.

Dieses Kapitel behandelt das instanceof-Muster, Typmuster in switch, bewachte Muster und null-Behandlung sowie Record-Muster – und verbindet sie in einem ausführbaren Programm. Es baut auf drei Funktionen auf, die Sie vielleicht zuerst nachlesen möchten: dem instanceof-Operator, Records und switch-Ausdrücken.

Pattern Matching für instanceof

Das klassische Test-und-Cast-Muster benötigte drei Referenzen auf denselben Typ. Das instanceof-Muster bindet eine Variable im selben Zug wie der Test, und die Bindung gilt überall dort, wo der Test als wahr bekannt ist.

Object value = "hello";

// Old way: test, then cast
if (value instanceof String) {
    String s = (String) value;
    System.out.println(s.length());
}

// Pattern way: test and bind together
if (value instanceof String s) {
    System.out.println(s.length());
}

Da die Bindungsvariable am booleschen Ausdruck teilnimmt, können Sie in derselben if-Anweisung weiter einengen. Der Compiler beweist, dass s sicher zu verwenden ist:

if (value instanceof String s && s.length() > 3) {
    System.out.println(s.toUpperCase());
}

Muster in switch

Ein switch kann auf Typmuster abgleichen und nach dem Laufzeittyp des Selektors dispatchen. Jeder case bindet den abgeglichenen Wert, sodass der Rumpf direkt mit einer typisierten Variable arbeitet. Dies wandelt lange if/else instanceof-Ketten in eine kompakte, lesbare Tabelle um.

static String format(Object value) {
    return switch (value) {
        case Integer i -> "int: " + i;
        case Long l    -> "long: " + l;
        case String s  -> "string: " + s;
        default        -> "other: " + value;
    };
}

Ein typmusterbasierter switch muss erschöpfend sein – er muss jede mögliche Eingabe abdecken. Für beliebige Object-Selektoren bedeutet das einen default-Zweig; für sealed-Hierarchien kennt der Compiler die vollständige Menge der Untertypen und kann die Erschöpflichkeit ohne default prüfen.

Bewachte Muster und null

Eine when-Klausel fügt einem case eine boolesche Bedingung hinzu und ermöglicht es zwei Werten desselben Typs, verschiedene Zweige zu nehmen. Dies wird als bewachtes Muster bezeichnet, und die Reihenfolge ist wichtig: spezifischere bewachte Cases kommen vor dem unbewachten Fallback.

static String size(String s) {
    return switch (s) {
        case String t when t.isEmpty() -> "empty";
        case String t when t.length() < 5 -> "short";
        case String t -> "long (" + t.length() + ")";
    };
}

Traditionell warf ein switch bei einem null-Selektor eine NullPointerException. Ein musterbasierter switch kann null explizit mit einem case null behandeln und die Null-Prüfung innerhalb desselben Konstrukts behalten, anstatt sie als separate Prüfung davor zu schreiben.

FunktionSyntaxZweck
Typmustercase String sNach Typ abgleichen und binden
Bewachtes Mustercase String s when s.isEmpty()Eine Bedingung zu einem Case hinzufügen
Null-Labelcase nullEinen null-Selektor abgleichen
Record-Mustercase Point(int x, int y)Einen Record destrukturieren

Record-Muster

Ein Record-Muster gleicht einen Record ab und bindet seine Komponenten in einem Schritt, sodass die Accessor-Aufrufe entfallen. Da Records ihre Komponenten offenlegen, kennt der Compiler die genaue Form und lässt Sie jeden Teil inline benennen. Record-Muster können verschachtelt werden, sodass Sie einen Record von Records destrukturieren können.

record Point(int x, int y) {}
record Line(Point start, Point end) {}

static String render(Object o) {
    return switch (o) {
        case Point(int x, int y) -> "point " + x + "," + y;
        // Nested: pull both endpoints' coordinates out at once
        case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
            "line " + x1 + "," + y1 + " -> " + x2 + "," + y2;
        default -> "unknown";
    };
}

Pattern Matching glänzt besonders mit sealed-Typen: Wenn eine Schnittstelle ihre zulässigen Implementierungen auflistet, ist ein switch darüber ohne default erschöpfend, und das Hinzufügen eines neuen Untertyps macht den fehlenden Case zu einem Compilerfehler statt einem stillen Bug.

Ein vollständiges, ausführbares Beispiel

Das folgende Programm verbindet alle Teile. Es verwendet ein instanceof-Muster mit einem Guard, eine sealed Shape-Hierarchie aus Records, Record-Muster, die jede Form in einem switch destrukturieren, ein bewachtes Muster, das ein Quadrat erkennt, und ein case null – alles ohne einen einzigen expliziten Cast.

java— editable, runs on the server

Was aus der Ausführung zu entnehmen ist:

  • describe(42) gibt positive int 42 aus, weil der Guard instanceof Integer i && i > 0 den Typ und den Wert zusammen prüft, bevor i gebunden wird.
  • describe(-5) fällt auf unknown zurück – dasselbe Integer-Muster stimmt mit dem Typ überein, aber der Guard i > 0 schlägt fehl, was zeigt, wie ein Guard ein Typmuster verfeinert.
  • Der area-Switch benötigt kein default: Shape ist sealed, daher ist das Auflisten von Circle, Rectangle und Triangle erschöpfend, und der Compiler ist zufrieden.
  • Das 5.0 x 5.0-Rechteck wird als square side=5.0 ausgegeben, weil sein bewachter when w == h-Case vor dem allgemeinen Rectangle r-Case platziert ist und gewinnt.
  • Die letzte Zeile gibt no shape aus: Der case null-Zweig behandelt einen null-Selektor innerhalb des Switch, anstatt eine NullPointerException zu werfen.

Übung

Übung
Was bewirkt das Hinzufügen einer 'when'-Klausel zu einem Case in einem musterbasierten switch?
Was bewirkt das Hinzufügen einer 'when'-Klausel zu einem Case in einem musterbasierten switch?
Was this page helpful?