JEP 405, Record Patterns (Preview)
Contexte
Le mois dernier, nous avons parlé du filtrage par motif pour le cas de l’instruction switch
. La première utilisation de ce principe a été pour le filtrage par motif pour instanceof
(JEP 406)
Pour rappel, nous avions avant ce type de code :
if (obj instanceof String) {
String s = (String) obj;
// utilisation de s
}
Maintenant, nous pouvons écrire simplement le code suivant :
if (obj instanceof String s) {
// utilisation de s en tant que chaine de caractere
... s.contains(..);
} else {
// pas possible d'utiliser la variable s
}
vous pouvez consulter ce billet pour avoir plus d’informations.
Côté des enregistrements (Record), cela a été introduit dans le JDK14 en mode aperçu.
Pour rappel, cela permet de définir des classes sous une forme simplifiée.
record Point(int x, int y) {}
Pour plus de précision, vous pouvez consulter ce billet.
Voilà pour le contexte, passons au principe de cette JEP.
Le 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);
}
}
Support des génériques
Prenons la définition suivante pour l’enregistrement Box
:
record Box<T>(T t) {}
Nous pouvons écrire le code pour le filtrage suivant :
static void test1(Box<Object> bo) {
if (bo instanceof Box<Object>(String s)) { // (1)
System.out.println("String " + s);
}
}
static void test2(Box<Object> bo) {
if (bo instanceof Box<String>(var s)) { // (2)
System.out.println("String " + s);
}
}
-
Déduction faite avec le type du paramètre s
-
Déduction faite avec le type dans les chevrons
Cela implique que le code ci-dessous ne compile pas
static void test3(Box<Object> bo) {
if (bo instanceof Box<Object>(var s)) { // Erreur
System.out.println("String " + s);
}
}
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
.
Cela 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.
Moteur de recherche
"Eduquer, ce n'est pas remplir des vases mais c'est d'allumer des feux." - Michel Montaigne
Billets récents
- Eclipse plante systématiquement sous Debian (et autres distribution Linux)
- JEP 463, Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Debian - Montée de version de Debian 11 (Bullseye) à Debian 12 (Bookworm)
- JEP 451, Prepare to Disallow the Dynamic Loading of Agents
- JEP 444, Virtual Threads