JEP 406 Pattern Matching for switch (Preview)
Contexte
Faisant suite à la JEP 394 sur le filtrage par motif pour instanceof
, nous avons la version du filtrage pour le switch
. Pour rappel, le filtrage par motif pour instanceof
permet la nouvelle écriture suivante :
// Ancien code
if (o instanceof String) {
String s = (String)o;
// s est utilisable
...
}
// Nouveau code
if (o instanceof String s) {
// s est utilisable
...
}
Vous retrouverez plus d’informations dans mon billet Billet sur les nouveautés de Java 16.
Le principe
Si nous avons des conditions imbriquées car il existe plusieurs types possibles. Nous obtenons le code suivant :
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
L’objectif est de pouvoir utiliser un switch
(et même une expression switch
plus exactement).
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
Le code est concis et claire. Et c’est cela qui est génial.
Gestion de la valeur nulle
La valeur null
n’est normalement pas autorisée pour un switch
. Il est nécessaire de le protéger. Cela donne généralement le code suivant :
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
Nous pouvons traiter simplement le cas de la valeur nulle avec le code suivant :
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
Cela fonctionne aussi si nous souhaitons traiter la valeur nulle avec une autre valeur.
static void testStringOrNull(Object o) {
switch (o) {
case null, String s -> System.out.println("String: " + s);
}
}
Pattern 'switch label'
Expressoin de sélection
Nous pouvons une selection sur les types suivants :
-
Classes
-
Enregistrements
-
Tableau
-
Cas par défaut
-
Cas du
null
cité plus haut.
record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }
static void typeTester(Object o) {
switch (o) {
case null -> System.out.println("null");
case String s -> System.out.println("String");
case Color c -> System.out.println("Color with " + c.values().length + " values");
case Point p -> System.out.println("Record class: " + p.toString());
case int[] ia -> System.out.println("Array of ints of length" + ia.length);
default -> System.out.println("Something else");
}
}
La dominance
l’exemple suivant ne fonctionne pas.
static void error(Object o) {
switch(o) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
case String s -> // Erreur - Le filtrage est dominé par le motif précédent.
System.out.println("A string: " + s);
default -> {
break;
}
}
}
En effet, toute chaine de caractère sera par héritage une instance de CharSequance
. Donc le premier motif est aussi valable.
La complétude
L’exemple ci-dessous ne fonctionne pas.
static int coverage(Object o) {
return switch (o) { // Erreur - incomplete
case String s -> s.length();
case Integer i -> i;
};
}
En effet, tous les cas ne sont pas traités. Il faut à minima inclure le cas default
. Cela donne le code suivant :
static int coverage(Object o) {
return switch (o) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}
Classes scéllées
Avec les classes scéllées, nous pouvons tous énumérer. Dans ce cas, le cas default n’est plus nécessaire.
sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {} // Implicitly final
static int testSealedCoverage(S s) {
return switch (s) {
case A a -> 1;
case B b -> 2;
case C c -> 3;
};
}
Valeur nulle
Normalement, la valeur nulle n’est pas autorisé dans un switch
. Cela lève une exception de type NullPointerException
static void test(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
Pour garder le comportement, cela équivaux au code précédent.
Filtrage gardé
static void test(Object o) {
switch (o) {
case null -> throw new NullPointerException();
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
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