JEP 440, Records Pattern

Contexte

Le principe est l’utilisation des motifs introduits par filtrage par motif pour instanceof, mais de l’appliquer pour les enregistrements.

astucePour plus d’informations sur le filtrage par motif pour instanceof, je vous conseille cette article JEP 394 - Pattern Matching for instanceof

Cette fonctionnalité a connu deux aperçu

Maintenant, c’est bien une fonctionnalité standard qui sera inclus dans le JDK 21.

Principe

L’objectif est d’écrire du code utilisant les deux fonctionnalités précédentes : Les enregistrements (Record) et les motifs (Patterns).

Prenons la définition de l’enregistrement Point suivant :

record Point(int x, int y) {}

D’ores et déjà, nous pouvons écrire le code suivant avec le filtrage par motif pour instanceof:

static void afficheSomme(Object o) {
    if (o instanceof Point p) {
        int a = p.x();
        int b = p.y();
        System.out.println(a + b);
    }
}

Les classes classiques sont basées sur le principe de l’encapsulation. C’est à dire que nous connaissons le contrat de services via l’interface mais nous ne connaissons pas le détail de l’implémentation.

A l’inverse, les enregistrements (Record) mettent en avant leur structure interne. L’enregistrement Point est composé d’une valeur x et d’une valeur y.

Par ce principe, nous allons pouvoir écrire l’instruction instanceof comme ceci :

void afficheSomme(Object o) {
    if (o instanceof Point(int a, int b)) {
        System.out.println(a + b);
    }
}

Pour aller plus loin

Nous pouvons manipuler des enregistrements plus complexe. Par exemple, prenons un enregistrement Rectangle qui est composé de deux Point:

record Rectangle(Point hautGauche, Point basDroit) {}

Par conséquent, nous pouvons écrire le code suivant :

static void afficheSommePointHautGauche(Rectangle r) {
    if (r instanceof Rectangle(Point hg, Point bd)) {
         System.out.println(hg.x() + hg.y());
    }
}

Ce qui nous intéresse dans le code est le point haut à gauche, donc nous pouvons ignorer le second point.

static void afficheSommePointHautGauche(Rectangle r) {
    if (r instanceof Rectangle(Point hg, var bd)) {
         System.out.println(hg.x() + hg.y());
    }
}

En revanche, nous savons qu’un Point contient deux valeurs. Par conséquent, nous pouvons appliquer le même principe.

static void afficheSommePointHautGauche(Rectangle r) {
    if (r instanceof Rectangle(Point(int a, int b) hg, var bd)) {
         System.out.println(a + b);
    }
}

Exhaustivité du switch

Prenons les définitions suivantes pour Forme, Triangle, Rectangle et Paire :

sealed interface Forme permits Triangle, Rectangle {}
final class Triangle implements Forme {}
final class Rectangle implements Forme {}
record Paire<T>(T x, T y) {}

Nous pouvons définir une variable comme celle-ci

Paire<Forme> p1 = ...;

L’interface Forme est une interface scellée. Cela peut être soit une classe Triangle, soit une classe de Rectangle. Nous pouvons écrire l’instruction switch suivante :

switch (p1) {
    case Paire<Forme>(Triangle  p, Forme s) -> ...
    case Paire<Forme>(Rectangle p, Forme s) -> ...
}

Ci-dessus, l’ensemble des cas de l’instruction switch est défini. Tous les combinaisons possibles sont traitées.

Maintenant, nous pouvons aussi écrire le code suivant :

switch (p1) {
    case Paire<Forme>(Triangle p, Forme s) -> ...
    case Paire<Forme>(Rectangle p, Triangle s) -> ...
    case Paire<Forme>(Rectangle p, Rectangle s) -> ...
}

Nous avons simplement décomposé le deuxième cas du switch (Rectangle p, Forme s) avec les différents cas possibles de Forme : la classe Triangle et la classe Rectangle.

Maintenant, regardons le code suivant :

switch (p2) { // Erreur !
    case Paire<Forme>(Triangle p, Rectangle s) -> ...
    case Paire<Forme>(Rectangle p, Triangle s) -> ...
    case Paire<Forme>(Forme p, Triangle s) -> ...
}

Ce dernier provoque une erreur de compilation car il manque le second cas avec le premier paramètre de type Forme et le second paramètre de type Rectangle.

Là encore, le compilateur nous aide pour traiter l’exhaustivité des cas pour l’écriture des switch.

astuceCela est très utile pour la mise en place ou si on ajoute une nouvelle classe dérivée à l’interface scellée Forme comme la classe Cercle par exemple. Le compilateur signalera tous les endroits où des cas ne sont pas traités.