Die Java-Sprache ist keine Ausnahme. Und Java 16 wird als letztes Feature die Unterstützung für den Mustervergleich für die Instanz des Operators hinzufügen.
Hoffentlich wird der Mustervergleich in Zukunft auf andere Sprachkonstrukte ausgeweitet.
Durch den Mustervergleich kann der Entwickler Code flexibler und hübscher schreiben und gleichzeitig verständlich halten.
Aber was ist, wenn Sie nicht von dem einen oder anderen Grund zu neuen Versionen von Java wechseln können? Glücklicherweise können Sie mit den Funktionen von Java 8 einige der Mustervergleichsfunktionen in Form einer Bibliothek implementieren.
Schauen wir uns einige Muster an und wie sie mit einer einfachen Bibliothek implementiert werden können.
Mit dem Konstantenmuster können Sie die Gleichheit mit Konstanten überprüfen. In Java können Sie mit der switch-Anweisung die Gleichheit von Zahlen, Aufzählungen und Zeichenfolgen überprüfen. Manchmal möchten Sie jedoch die Gleichheit von Objektkonstanten mithilfe 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);
};
Ein ähnlicher Code kann wie folgt geschrieben werden. Gleichzeitig werden unter der Haube Werte verglichen und in einer if-Anweisung überprüft. Sie können sowohl ein Anweisungsformular als auch einen Ausdruck verwenden.
Es ist auch sehr einfach, mit Wertebereichen zu arbeiten.
import static org.kl.jpml.pattern.ConstantPattern.*;
matches(data).as(
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)
);
matches(data).as(
or(1, 2), () -> System.out.println("1 or 2"),
in(3, 6), () -> System.out.println("between 3 and 6"),
in(7), () -> System.out.println("7"),
Null.class, () -> System.out.println("Null value "),
Else.class, () -> System.out.println("Default value: " + data)
);
Mit dem Tupelmuster können Sie die Gleichheit mehrerer Variablen mit Konstanten gleichzeitig überprüfen.
var (side, width) = border;
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 ");
};
for ((side, width) : listBorders) {
System.out.println("border: " + [side + "," + width]);
}
In diesem Fall kann es nicht nur in Form eines Schalters verwendet, sondern auch in übereinstimmende zerlegt oder nacheinander in einer Schleife übergeben werden.
import static org.kl.jpml.pattern.TuplePattern.*;
let(border, (String side, int width) -> {
System.out.println("border: " + side + "," + width);
});
matches(side, width).as(
of("top", 25), () -> System.out.println("top"),
of("bottom", 30), () -> System.out.println("bottom"),
of("left", 15, () -> System.out.println("left"),
of("right", 15), () -> System.out.println("right"),
Null.class, () -> System.out.println("Null value"),
Else.class, () -> System.out.println("Default value")
);
foreach(listBorders, (String side, int width) -> {
System.out.println("border: " + side + "," + width);
}
Mit dem Typprüfmuster können Sie den Typ abgleichen und gleichzeitig den Wert einer Variablen extrahieren.
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);
};
In Java müssen wir dazu zuerst den Typ überprüfen, in den Typ umwandeln und ihn dann einer neuen Variablen zuweisen. Dieses Muster erleichtert Ihren Code erheblich.
import static org.kl.jpml.pattern.VerifyPattern.matches;
matches(data).as(
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 den Typ abgleichen und gleichzeitig 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);
};
Ein ähnliches Design kann wie folgt implementiert werden. Um das Schreiben von Bedingungen zu vereinfachen, können Sie die folgenden Vergleichsfunktionen verwenden: lessThan / lt, GreaterThan / gt, lessThanOrEqual / le, GreaterThanOrEqual / ge, same / eq, notEqual / ne. Und um die Bedingungen wegzulassen, können Sie ändern: immer / ja, nie / nein.
import static org.kl.jpml.pattern.GuardPattern.matches;
matches(data).as(
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); }
);
matches(data).as(
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 einen Typ zuordnen und ein Objekt in seine Komponenten zerlegen.
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));
}
In Java müssen wir dazu zuerst den Typ überprüfen, in den Typ umwandeln, ihn einer neuen Variablen zuweisen und erst dann über Getter auf die Klassenfelder zugreifen.
import static org.kl.jpml.pattern.DeconstructPattern.*;
Figure figure = new Rectangle();
let(figure, (int w, int h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure).as(
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 die Komponente zu erhalten, muss die Klasse außerdem über eine oder mehrere Dekonstruktionsmethoden verfügen. Diese Methoden sollten mit Anmerkungen versehen werden Extrakt...
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 von IntRef, FloatRef usw. verwenden.
Um den Overhead durch Reflektion zu reduzieren, werden Caching und Tricks mit der Standardklasse LambdaMetafactory verwendet.
@Extract
public void deconstruct(IntRef width, IntRef height) {
width.set(this.width);
height.set(this.height);
}
Mit dem Eigenschaftsmuster können Sie gleichzeitig den Typ abgleichen und über ihre Namen auf die Klassenfelder zugreifen.
let (w: int w, h:int h) = figure;
switch (figure) {
case Rectangle(w: int w == 5, h: int h == 10) -> out.println("sqr: " + (w * h));
case Rectangle(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));
}
Dies ist eine vereinfachte Form des Dekonstruktionsmusters, bei der Sie nur bestimmte Klassenfelder dekonstruieren müssen.
Um den Overhead durch Reflektion zu reduzieren, werden Caching und Tricks mit der Standardklasse LambdaMetafactory verwendet.
import static org.kl.jpml.pattern.PropertyPattern.*;
Figure figure = new Rectangle();
let(figure, of("w", "h"), (int w, int h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure).as(
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 mit Methodenreferenzen 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).as(
Rect.class, Rect::w, Rect::h, (int w, int h) -> System.out.println("sqr: " + (w * h)),
Circle.class, Circle::r, (int r) -> System.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 die Feldwerte in der Reihenfolge der Deklaration überprü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);
};
In Java müssen wir dazu zuerst den Typ überprüfen, in den Typ umwandeln, ihn einer neuen Variablen zuweisen und erst dann über Getter auf die Klassenfelder zugreifen und auf Gleichheit prüfen.
Um den Overhead durch Reflektion zu reduzieren, wird Caching verwendet.
import static org.kl.jpml.pattern.PositionPattern.*;
matches(data).as(
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 validieren möchte, sollten diese Felder mit Anmerkungen markiert werden Ausschließen... Diese Felder sollten zuletzt deklariert werden.
class Circle {
private int radius;
@Exclude
private int temp;
}
Mit dem statischen Muster können Sie den Typ gleichzeitig abgleichen und ein Objekt mithilfe von Factory-Methoden dekonstruieren.
switch (some) {
case Result.value(var v) -> System.out.println("value: " + v)
case Result.error(var e) -> System.out.println("error: " + e)
default -> System.out.println("Default value")
};
Ähnlich wie beim Dekonstruktionsmuster, jedoch der Name der mit Anmerkungen versehenen Dekonstruktionsmethoden Extraktmuss explizit angegeben werden.
Um den Overhead durch Reflektion zu reduzieren, werden Caching und Tricks mit der Standardklasse LambdaMetafactory verwendet.
import static org.kl.jpml.pattern.StaticPattern.*;
matches(figure).as(
Result.class, of("value"), (var v) -> System.out.println("value: " + v),
Result.class, of("error"), (var e) -> System.out.println("error: " + e),
Else.class, () -> System.out.println("Default value")
);
Das Sequenzmuster erleichtert die Verarbeitung von Datensequenzen.
List<Integer> list = ...;
switch (list) {
case empty() -> System.out.println("Empty value")
case head(var h) -> System.out.println("list head: " + h)
case tail(var t) -> System.out.println("list tail: " + t)
default -> System.out.println("Default value")
};
Mit Bibliotheksmethoden können Sie einfach mit Datensequenzen arbeiten.
import static org.kl.jpml.pattern.SequencePattern.*;
List<Integer> list = List.of(1, 2, 3);
matches(figure).as(
empty() () -> System.out.println("Empty value"),
head(), (var h) -> System.out.println("list head: " + h),
tail(), (var t) -> System.out.println("list tail: " + t),
Else.class, () -> System.out.println("Default value")
);
Um den Code zu vereinfachen, können Sie auch die folgenden Funktionen verwenden, die in modernen Sprachen als Sprachmerkmale oder -funktionen angesehen werden können.
import static org.kl.jpml.pattern.CommonPattern.*;
var rect = lazy(Rectangle::new);
var result = elvis(rect.get(), 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")
);
repeat(3, () -> {
System.out.println("three time");
)
int even = self(number).takeIf(it -> it % 2 == 0);
int odd = self(number).takeUnless(it -> it % 2 == 0);
Wie Sie sehen können, ist Pattern Matching ein leistungsstarkes Tool, das das Schreiben von Code erheblich vereinfacht. Mit den Funktionen von Java 8 können Sie die Funktionen des Mustervergleichs mithilfe der Sprache emulieren.
Der Quellcode der Bibliothek kann auf github: link eingesehen werden . Ich würde mich über Feedback und Verbesserungsvorschläge freuen.