W3docs

Filtrage par motif en Java

Utilisez le filtrage par motif en Java pour instanceof et switch — motifs de type, motifs de record et déconstruction.

Pendant des années, le code Java travaillant avec des valeurs de type inconnu suivait un rituel fastidieux : tester le type avec instanceof, puis effectuer un cast vers ce type, puis l'utiliser. Le filtrage par motif compresse ce rituel en une seule expression. Un motif décrit la forme d'une donnée ; si une valeur correspond, Java lie ses parties à des variables utilisables immédiatement — aucun cast manuel n'est requis.

Le filtrage par motif est arrivé par étapes : d'abord les motifs instanceof, puis les motifs dans switch, puis les motifs de record qui déconstruisent les records en leurs composants. Ensemble, ils permettent d'écrire du code déclaratif et sûr au niveau des types, qui reflète la structure des données qu'il manipule.

Ce chapitre couvre le motif instanceof, les motifs de type dans switch, les motifs gardés et la gestion de null, et les motifs de record — puis les réunit dans un programme exécutable. Il s'appuie sur trois fonctionnalités qu'il peut être utile de revoir en premier : l'opérateur instanceof, les records et les expressions switch.

Filtrage par motif pour instanceof

Le schéma classique test-et-cast nécessitait trois références au même type. Le motif instanceof lie une variable au même moment que le test, et la liaison est accessible partout où le test est connu pour être vrai.

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());
}

Comme la variable liée participe à l'expression booléenne, vous pouvez continuer à affiner dans le même if. Le compilateur prouve que s est sûre à utiliser :

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

Motifs dans switch

Un switch peut correspondre à des motifs de type, en distribuant selon le type d'exécution du sélecteur. Chaque case lie la valeur correspondante, de sorte que le corps travaille directement avec une variable typée. Cela transforme de longues chaînes if/else instanceof en un tableau compact et lisible.

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;
    };
}

Un switch à motifs de type doit être exhaustif — il doit couvrir toutes les entrées possibles. Pour les sélecteurs Object arbitraires, cela signifie une branche default ; pour les hiérarchies sealed, le compilateur connaît l'ensemble complet des sous-types et peut vérifier l'exhaustivité sans default.

Motifs gardés et null

Une clause when ajoute une condition booléenne à un case, permettant à deux valeurs du même type d'emprunter des branches différentes. C'est ce qu'on appelle un motif gardé, et l'ordre est important : les cas gardés plus spécifiques viennent avant le cas non gardé de secours.

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() + ")";
    };
}

Traditionnellement, un switch lançait une NullPointerException sur un sélecteur null. Un switch à motifs peut gérer null explicitement avec un case null, gardant la vérification du null à l'intérieur du même construct plutôt qu'une garde séparée avant lui.

FonctionnalitéSyntaxeRôle
Motif de typecase String sCorrespondre par type et lier
Motif gardécase String s when s.isEmpty()Ajouter une condition à un cas
Étiquette nullcase nullCorrespondre à un sélecteur null
Motif de recordcase Point(int x, int y)Déconstruire un record

Motifs de record

Un motif de record correspond à un record et lie ses composants en une seule opération, évitant ainsi les appels aux accesseurs. Comme les records exposent leurs composants, le compilateur connaît la forme exacte et vous permet de nommer chaque partie en ligne. Les motifs de record s'imbriquent, vous permettant ainsi de destructurer un record de records.

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";
    };
}

Le filtrage par motif excelle avec les types scellés : lorsqu'une interface liste ses implémentations autorisées, un switch sur celles-ci est exhaustif sans default, et l'ajout d'un nouveau sous-type transforme le cas manquant en erreur de compilation plutôt qu'en bug silencieux.

Un exemple complet et exécutable

Le programme ci-dessous réunit tous les éléments. Il utilise un motif instanceof avec une garde, une hiérarchie Shape scellée de records, des motifs de record qui déconstruisent chaque forme dans un switch, un motif gardé qui détecte un carré, et un case null — le tout sans un seul cast explicite.

java— editable, runs on the server

Ce que l'on retient de l'exécution :

  • describe(42) affiche positive int 42 parce que la garde instanceof Integer i && i > 0 teste le type et la valeur ensemble avant de lier i.
  • describe(-5) tombe dans unknown — le même motif Integer correspond au type, mais la garde i > 0 échoue, montrant comment une garde affine un motif de type.
  • Le switch de area n'a pas besoin de default : Shape est scellé, donc lister Circle, Rectangle et Triangle est exhaustif et le compilateur est satisfait.
  • Le rectangle 5.0 x 5.0 s'affiche comme square side=5.0 parce que son cas gardé when w == h est placé avant le cas général Rectangle r et l'emporte.
  • La dernière ligne affiche no shape : la branche case null gère un sélecteur null à l'intérieur du switch au lieu de lancer une NullPointerException.

Pratique

Pratique
Dans un switch à motifs, qu'est-ce qu'ajouter une clause 'when' à un cas fait-il ?
Dans un switch à motifs, qu'est-ce qu'ajouter une clause 'when' à un cas fait-il ?
Was this page helpful?