De nombreuses langues modernes prennent en charge la correspondance de modèles au niveau de la langue. Java ne prend actuellement pas en charge la correspondance de modèles, mais il y a de l'espoir que les choses puissent changer à l'avenir.
La comparaison avec l'exemple révèle au développeur la possibilité d'écrire du code de manière plus flexible et plus belle, tout en le laissant compréhensible.
En utilisant les capacités de Java 8, vous pouvez implémenter certaines fonctionnalités de correspondance de modèles sous la forme d'une bibliothèque. Dans ce cas, vous pouvez utiliser à la fois l'instruction et l'expression.
Le modèle constant permet de vérifier l'égalité avec les constantes. En Java, un commutateur vous permet de vérifier des nombres, des énumérations et des chaînes égaux. Mais parfois, je veux vérifier l'égalité des constantes des objets en utilisant la méthode equals ().
switch (data) { case new Person("man") -> System.out.println("man"); case new Person("woman") -> System.out.println("woman"); case new Person("child") -> System.out.println("child"); case null -> System.out.println("Null value "); default -> System.out.println("Default value: " + data); };
Pour différentes options de branchement, vous devez utiliser les surcharges de la méthode matches (). Par conséquent, pour le moment, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 6 constantes.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.ConstantPattern.matches; matches(data, new Person("man"), () -> System.out.println("man"); new Person("woman"), () -> System.out.println("woman"); new Person("child"), () -> System.out.println("child"); Null.class, () -> System.out.println("Null value "), Else.class, () -> System.out.println("Default value: " + data) );
Le modèle de tuple vous permet de vérifier simultanément l'égalité de plusieurs modifications avec des constantes.
switch (side, width) { case "top", 25 -> System.out.println("top"); case "bottom", 30 -> System.out.println("bottom"); case "left", 15 -> System.out.println("left"); case "right", 15 -> System.out.println("right"); case null -> System.out.println("Null value "); default -> System.out.println("Default value "); };
À l'heure actuelle, la bibliothèque prend en charge la possibilité de spécifier 4 variables et 6 branches.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.TuplePattern.matches; matches(side, width, "top", 25, () -> System.out.println("top"); "bottom", 30, () -> System.out.println("bottom"); "left", 15, () -> System.out.println("left"); "right", 15, () -> System.out.println("right"); Null.class, () -> System.out.println("Null value"), Else.class, () -> System.out.println("Default value") );
Le modèle de test de type vous permet de faire correspondre simultanément le type et d'extraire la valeur de la variable. En Java, pour cela, nous devons d'abord vérifier le type, transtyper en type puis l'affecter à une nouvelle variable.
switch (data) { case Integer i -> System.out.println(i * i); case Byte b -> System.out.println(b * b); case Long l -> System.out.println(l * l); case String s -> System.out.println(s * s); case null -> System.out.println("Null value "); default -> System.out.println("Default value: " + data); };
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 6 types.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.VerifyPattern.matches; matches(data, Integer.class, i -> { System.out.println(i * i); }, Byte.class, b -> { System.out.println(b * b); }, Long.class, l -> { System.out.println(l * l); }, String.class, s -> { System.out.println(s * s); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Le motif de garde vous permet de faire correspondre simultanément le type et de vérifier les conditions.
switch (data) { case Integer i && i != 0 -> System.out.println(i * i); case Byte b && b > -1 -> System.out.println(b * b); case Long l && l < 5 -> System.out.println(l * l); case String s && !s.empty() -> System.out.println(s * s); case null -> System.out.println("Null value "); default -> System.out.println("Default: " + data); };
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 6 types et conditions.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.GuardPattern.matches; matches(data, Integer.class, i -> i != 0, i -> { System.out.println(i * i); }, Byte.class, b -> b > -1, b -> { System.out.println(b * b); }, Long.class, l -> l == 5, l -> { System.out.println(l * l); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Pour simplifier l'écriture de la condition, le développeur peut utiliser les fonctions de comparaison suivantes: lessThan / lt, GreaterThan / gt, lessThanOrEqual / le, GreaterThanOrEqual / ge,
égal / eq, notEqual / ne. Et pour omettre les conditions, vous pouvez essayer: toujours / oui, jamais / non.
matches(data, Integer.class, ne(0), i -> { System.out.println(i * i); }, Byte.class, gt(-1), b -> { System.out.println(b * b); }, Long.class, eq(5), l -> { System.out.println(l * l); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Le modèle de déconstruction vous permet de faire correspondre simultanément le type et de décomposer l'objet en composants. En Java, pour cela, nous devons d'abord vérifier le type, convertir en un type, l'affecter à une nouvelle variable, puis accéder uniquement aux champs de la classe via des getters.
let (int w, int h) = figure; switch (figure) { case Rectangle(int w, int h) -> out.println("square: " + (w * h)); case Circle(int r) -> out.println("square: " + (2 * Math.PI * r)); default -> out.println("Default square: " + 0); }; for ((int w, int h) : listFigures) { System.out.println("square: " + (w * h)); }
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 3 types et de décomposer l'objet en 3 composants.
import org.kl.state.Else; import static org.kl.pattern.DeconstructPattern.matches; import static org.kl.pattern.DeconstructPattern.foreach; import static org.kl.pattern.DeconstructPattern.let; Figure figure = new Rectangle(); let(figure, (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rectangle.class, (int w, int h) -> out.println("square: " + (w * h)), Circle.class, (int r) -> out.println("square: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default square: " + 0) ); foreach(listRectangles, (int w, int h) -> { System.out.println("square: " + (w * h)); });
De plus, pour obtenir un composant, une classe doit avoir une ou plusieurs méthodes de déconstruction. Ces méthodes doivent être étiquetées avec Annotation Extract .
Tous les paramètres doivent être ouverts. Comme les primitives ne peuvent pas être transmises à une méthode par référence, vous devez utiliser des wrappers pour les primitives IntRef, FloatRef, etc.
import org.kl.type.IntRef; ... @Extract public void deconstruct(IntRef width, IntRef height) { width.set(this.width); height.set(this.height); }
En utilisant également Java 11, vous pouvez déduire des types de paramètres de déconstruction.
Figure figure = new Rectangle(); let(figure, (var w, var h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rectangle.class, (var w, var h) -> out.println("square: " + (w * h)), Circle.class, (var r) -> out.println("square: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default square: " + 0) ); foreach(listRectangles, (var w, var h) -> { System.out.println("square: " + (w * h)); });
Le modèle de propriété vous permet de faire correspondre simultanément le type et d'accéder aux champs de la classe par leurs noms. Au lieu de fournir tous les champs, vous pouvez accéder aux champs dont vous avez besoin dans n'importe quel ordre.
let (w: int w, h:int h) = figure; switch (figure) { case Rect(w: int w == 5, h: int h == 10) -> out.println("sqr: " + (w * h)); case Rect(w: int w == 10, h: int h == 15) -> out.println("sqr: " + (w * h)); case Circle (r: int r) -> out.println("sqr: " + (2 * Math.PI * r)); default -> out.println("Default sqr: " + 0); }; for ((w: int w, h: int h) : listRectangles) { System.out.println("square: " + (w * h)); }
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 3 types et de décomposer l'objet en 3 composants.
import org.kl.state.Else; import static org.kl.pattern.PropertyPattern.matches; import static org.kl.pattern.PropertyPattern.foreach; import static org.kl.pattern.PropertyPattern.let; import static org.kl.pattern.PropertyPattern.of; Figure figure = new Rectangle(); let(figure, of("w", "h"), (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rect.class, of("w", 5, "h", 10), (int w, int h) -> out.println("sqr: " + (w * h)), Rect.class, of("w", 10, "h", 15), (int w, int h) -> out.println("sqr: " + (w * h)), Circle.class, of("r"), (int r) -> out.println("sqr: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default sqr: " + 0) ); foreach(listRectangles, of("x", "y"), (int w, int h) -> { System.out.println("square: " + (w * h)); });
Vous pouvez également utiliser une autre méthode pour simplifier les champs de dénomination.
Figure figure = new Rect(); let(figure, Rect::w, Rect::h, (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rect.class, Rect::w, Rect::h, (int w, int h) -> out.println("sqr: " + (w * h)), Circle.class, Circle::r, (int r) -> out.println("sqr: " + (2 * Math.PI * r)), Else.class, () -> System.out.println("Default sqr: " + 0) ); foreach(listRectangles, Rect::w, Rect::h, (int w, int h) -> { System.out.println("square: " + (w * h)); });
Le modèle de position vous permet de faire correspondre simultanément le type et de vérifier la valeur des champs dans l'ordre de déclaration. En Java, pour cela, nous devons d'abord vérifier le type, le convertir en un type, l'affecter à une nouvelle variable, puis accéder aux champs de la classe via des getters et vérifier l'égalité.
switch (data) { case Circle(5) -> System.out.println("small circle"); case Circle(15) -> System.out.println("middle circle"); case null -> System.out.println("Null value "); default -> System.out.println("Default value: " + data); };
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 6 types et de vérifier 4 champs à la fois.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.PositionPattern.matches; import static org.kl.pattern.PositionPattern.of; matches(data, Circle.class, of(5), () -> { System.out.println("small circle"); }, Circle.class, of(15), () -> { System.out.println("middle circle"); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
De plus, si le développeur ne souhaite pas vérifier certains champs, ces champs doivent être marqués avec des annotations @Exclude. Ces champs doivent être déclarés en dernier.
class Circle { private int radius; @Exclude private int temp; }
Le modèle statique vous permet de faire correspondre simultanément le type et de déconstruire l'objet à l'aide de méthodes d'usine.
Optional some = ...; switch (some) { case Optional.empty() -> System.out.println("empty value"); case Optional.of(var v) -> System.out.println("value: " + v); default -> System.out.println("Default value"); };
À l'heure actuelle, la bibliothèque prend en charge la possibilité de vérifier les valeurs d'une variable avec 6 types et de décomposer l'objet en 3 composants.
import org.kl.state.Else; import static org.kl.pattern.StaticPattern.matches; import static org.kl.pattern.StaticPattern.of; Optional some = ...; matches(figure, Optinal.class, of("empty"), () -> System.out.println("empty value"), Optinal.class, of("of") , (var v) -> System.out.println("value: " + v), Else.class, () -> System.out.println("Default value") );
Dans le même temps, pour obtenir le composant, la classe doit avoir une ou plusieurs méthodes de déconstruction, intitulées Extraire les annotations.
@Extract public void of(IntRef value) { value.set(this.value); }
Le modèle de séquence facilite le traitement des séquences de données.
List<Integer> list = ...; switch (list) { case Ranges.head(var h) -> System.out.println("list head: " + h); case Ranges.tail(var t) -> System.out.println("list tail: " + t); case Ranges.empty() -> System.out.println("Empty value"); default -> System.out.println("Default value"); };
En utilisant la bibliothèque, vous pouvez écrire comme suit.
import org.kl.state.Else; import org.kl.range.Ranges; import static org.kl.pattern.SequencePattern.matches; List<Integer> list = ...; matches(figure, Ranges.head(), (var h) -> System.out.println("list head: " + h), Ranges.tail(), (var t) -> System.out.println("list tail: " + t), Ranges.empty() () -> System.out.println("Empty value"), Else.class, () -> System.out.println("Default value") );
De plus, pour simplifier le code, vous pouvez utiliser les fonctions suivantes.
import static org.kl.pattern.CommonPattern.with; import static org.kl.pattern.CommonPattern.when; Rectangle rect = new Rectangle(); with(rect, it -> { it.setWidth(5); it.setHeight(10); }); when( side == Side.LEFT, () -> System.out.println("left value"), side == Side.RIGHT, () -> System.out.println("right value") );
Comme vous pouvez le voir, la correspondance de motifs est un outil puissant qui facilite beaucoup l'écriture de code. En utilisant des expressions lambda, des références de méthode et la dérivation de types de paramètres lambda, vous pouvez émuler les possibilités de correspondance de motifs par le biais même du langage.
Le code source de la bibliothèque est ouvert et disponible sur github .