Viele moderne Sprachen unterstützen den Mustervergleich auf Sprachebene. Java unterstützt derzeit keinen Mustervergleich, aber es besteht die Hoffnung, dass sich die Dinge in Zukunft ändern können.
Ein Vergleich mit dem Beispiel zeigt dem Entwickler, dass er Code flexibler und schöner schreiben kann, während er verständlich bleibt.
Mit den Funktionen von Java 8 können Sie einige der Funktionen des Mustervergleichs in Form einer Bibliothek implementieren. In diesem Fall können Sie sowohl die Anweisung als auch den Ausdruck verwenden.
Das konstante Muster ermöglicht die Überprüfung der Gleichheit mit Konstanten. In Java können Sie mit einem Schalter nach gleichen Zahlen, Aufzählungen und Zeichenfolgen suchen. Aber manchmal möchte ich die Gleichheit der Konstanten von Objekten mit der equals () -Methode überprüfen.
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); };
Für verschiedene Verzweigungsoptionen müssen Sie die Überladungen der match () -Methode verwenden. Daher unterstützt die Bibliothek derzeit die Möglichkeit, die Werte einer Variablen mit 6 Konstanten zu überprüfen.
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) );
Mit dem Tupelmuster können Sie die Gleichheit mehrerer Änderungen mit Konstanten gleichzeitig überprüfen.
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 "); };
Derzeit unterstützt die Bibliothek die Angabe von 4 Variablen und 6 Zweigen.
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") );
Mit dem Typprüfmuster können Sie gleichzeitig den Typ abgleichen und den Wert der Variablen extrahieren. In Java müssen wir dazu zuerst den Typ überprüfen, in den Typ umwandeln und ihn dann einer neuen Variablen zuweisen.
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); };
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 6 Typen zu überprüfen.
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); } );
Mit dem Schutzmuster können Sie gleichzeitig den Typ anpassen und nach Bedingungen suchen.
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); };
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 6 Typen und Bedingungen zu überprüfen.
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); } );
Um das Schreiben der Bedingung zu vereinfachen, kann der Entwickler die folgenden Funktionen zum Vergleich verwenden: lessThan / lt, GreaterThan / gt, lessThanOrEqual / le, GreaterThanOrEqual / ge,
gleich / äq, nicht gleich / ne. Und um die Bedingungen wegzulassen, können Sie versuchen: immer / ja, nie / nein.
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); } );
Mit dem Dekonstruktionsmuster können Sie gleichzeitig den Typ abgleichen und das Objekt in Komponenten zerlegen. In Java müssen wir dazu zuerst den Typ überprüfen, in einen Typ umwandeln, ihn einer neuen Variablen zuweisen und erst dann über Getter auf die Felder der Klasse zugreifen.
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)); }
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 3 Typen zu überprüfen und das Objekt in 3 Komponenten zu zerlegen.
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)); });
Um eine Komponente zu erhalten, muss eine Klasse über eine oder mehrere Dekonstruktionsmethoden verfügen. Diese Methoden sollten mit Annotation Extract gekennzeichnet sein .
Alle Parameter müssen offen sein. Da Grundelemente nicht als Referenz an eine Methode übergeben werden können, müssen Sie Wrapper für Grundelemente IntRef, FloatRef usw. verwenden.
import org.kl.type.IntRef; ... @Extract public void deconstruct(IntRef width, IntRef height) { width.set(this.width); height.set(this.height); }
Auch mit Java 11 können Sie auf Arten von Dekonstruktionsparametern schließen.
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)); });
Mit dem Eigenschaftsmuster können Sie gleichzeitig den Typ abgleichen und anhand ihrer Namen auf die Felder der Klasse zugreifen. Anstatt alle Felder anzugeben, können Sie in beliebiger Reihenfolge auf die benötigten Felder zugreifen.
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)); }
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 3 Typen zu überprüfen und das Objekt in 3 Komponenten zu zerlegen.
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)); });
Sie können auch eine andere Methode verwenden, um die Benennung von Feldern zu vereinfachen.
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)); });
Mit dem Positionsmuster können Sie gleichzeitig den Typ abgleichen und den Wert der Felder in der Deklarationsreihenfolge überprüfen. In Java müssen wir dazu zuerst den Typ überprüfen, ihn in einen Typ umwandeln, ihn einer neuen Variablen zuweisen und erst dann über Getter auf die Felder der Klasse zugreifen und auf Gleichheit prüfen.
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); };
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 6 Typen und 4 Feldern gleichzeitig zu überprüfen.
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); } );
Wenn der Entwickler einige Felder nicht überprüfen möchte, sollten diese Felder mit @ Exclude-Anmerkungen markiert werden. Diese Felder müssen zuletzt deklariert werden.
class Circle { private int radius; @Exclude private int temp; }
Mit dem statischen Muster können Sie den Typ gleichzeitig abgleichen und das Objekt mithilfe von Factory-Methoden dekonstruieren.
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"); };
Derzeit unterstützt die Bibliothek die Möglichkeit, die Werte einer Variablen mit 6 Typen zu überprüfen und das Objekt in 3 Komponenten zu zerlegen.
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") );
Gleichzeitig muss die Klasse über eine oder mehrere Dekonstruktionsmethoden verfügen, die als Annotationen extrahieren bezeichnet werden, um die Komponente abzurufen.
@Extract public void of(IntRef value) { value.set(this.value); }
Das Sequenzmuster erleichtert die Verarbeitung von Datensequenzen.
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"); };
Mit der Bibliothek können Sie wie folgt schreiben.
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") );
Um den Code zu vereinfachen, können Sie auch die folgenden Funktionen verwenden.
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") );
Wie Sie sehen können, ist Pattern Matching ein leistungsstarkes Tool, das das Schreiben von Code erheblich vereinfacht. Mithilfe von Lambda-Ausdrücken, Methodenreferenzen und der Ableitung von Arten von Lambda-Parametern können Sie die Möglichkeiten des Mustervergleichs mithilfe der Sprache emulieren.
Der Quellcode der Bibliothek ist offen und auf github verfügbar.