Nicht weit entfernt ist die neue,
14. Version von Java, was bedeutet, dass es Zeit ist zu sehen, welche neuen Syntaxfunktionen diese Version von Java enthalten wird. Eine dieser syntaktischen Möglichkeiten ist der
Mustervergleich des Typs , der unter Verwendung der verbesserten (erweiterten)
instanceof
Operators implementiert wird.
Heute möchte ich mit diesem neuen Operator herumspielen und die Funktionen seiner Arbeit detaillierter betrachten. Da die Mustererkennung nach Typ noch nicht im Haupt-JDK-Repository vorhanden ist, musste ich das Repository
des Amber-Projekts herunterladen, das neue Java-Syntaxkonstrukte entwickelt, und
das JDK aus diesem Repository
kompilieren .
Als erstes überprüfen wir die Java-Version, um sicherzustellen, dass wir wirklich JDK 14 verwenden:
> java -version openjdk version "14-internal" 2020-03-17 OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber) OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)
Alles ist richtig.
Jetzt schreiben wir ein kleines Stück Code mit der "alten"
instanceof
Operators und führen es aus:
public class A { public static void main(String[] args) { new A().f("Hello, world!"); } public void f(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.toLowerCase()); } } }
> java A.java hello, world!
Es funktioniert Dies ist eine Standardtypprüfung, gefolgt von einer Besetzung. Wir schreiben jeden Tag ähnliche Konstruktionen, egal welche Java-Version wir verwenden, mindestens 1.0, mindestens 13.
Aber jetzt haben wir Java 14 in unseren Händen und schreiben den Code mit der verbesserten
instanceof
Operators neu (ich werde in Zukunft darauf verzichten, Codezeilen zu wiederholen):
if (obj instanceof String str) { System.out.println(str.toLowerCase()); }
> java --enable-preview --source 14 A.java hello, world!
Großartig. Der Code ist sauberer, kürzer, sicherer und lesbarer. Es gab drei Wiederholungen des Wortes String, eine wurde. Beachten Sie, dass wir nicht vergessen haben, die Argumente
--enable-preview --source 14
, as anzugeben Der neue Operator ist eine
Vorschaufunktion . Darüber hinaus hat ein aufmerksamer Leser wahrscheinlich bemerkt, dass wir die A.java-Quelldatei direkt ohne Kompilierung ausgeführt haben. Diese Funktion
erschien in Java 11.
Versuchen wir, etwas komplexeres zu schreiben und fügen eine zweite Bedingung hinzu, die die gerade deklarierte Variable verwendet:
if (obj instanceof String str && str.length() > 5) { System.out.println(str.toLowerCase()); }
Es kompiliert und funktioniert. Aber was ist, wenn Sie die Bedingungen tauschen?
if (str.length() > 5 && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: cannot find symbol if (str.length() > 5 && obj instanceof String str) { ^
Kompilierungsfehler. Was zu erwarten ist: Die Variable
str
wurde noch nicht deklariert, kann also nicht verwendet werden.
Was ist übrigens mit der Veränderlichkeit? Ist die Variable final oder nicht? Wir versuchen:
if (obj instanceof String str) { str = "World, hello!"; System.out.println(str.toLowerCase()); }
A.java:8: error: pattern binding str may not be assigned str = "World, hello!"; ^
Ja, die letzte Variable. Dies bedeutet, dass das Wort „Variable“ hier nicht ganz richtig ist. Und der Compiler verwendet den Spezialbegriff "Musterbindung". Daher schlage ich von nun an vor, nicht "variabel", sondern "Musterbindung" zu sagen (das Wort "Bindung" ist leider nicht sehr gut ins Russische übersetzt).
Mit Veränderlichkeit und Terminologie aussortiert. Lass uns weiter experimentieren. Was ist, wenn es uns gelingt, den Compiler zu "brechen"?
Was ist, wenn Sie eine Variable und ein Muster mit demselben Namen benennen?
if (obj instanceof String obj) { System.out.println(obj.toLowerCase()); }
A.java:7: error: variable obj is already defined in method f(Object) if (obj instanceof String obj) { ^
Ist logisch. Das Überlappen einer Variablen aus dem äußeren Bereich funktioniert nicht. Dies ist äquivalent dazu, als ob wir die Variable
obj
zweites Mal im selben Bereich
obj
.
Und wenn ja:
if (obj instanceof String str && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: illegal attempt to redefine an existing match binding if (obj instanceof String str && obj instanceof String str) { ^
Der Compiler ist solide wie Beton.
Was können Sie noch versuchen? Lassen Sie uns mit den Bereichen herumspielen. Wenn Bindung in der
if
Verzweigung definiert ist, wird sie in der
else
Verzweigung definiert, wenn die Bedingung invertiert ist?
if (!(obj instanceof String str)) { System.out.println("not a string"); } else { System.out.println(str.toLowerCase()); }
Es hat funktioniert Der Compiler ist nicht nur zuverlässig, sondern auch intelligent.
Und wenn ja
if (obj instanceof String str && true) { System.out.println(str.toLowerCase()); }
Es hat wieder funktioniert. Der Compiler versteht richtig, dass die Bedingung auf eine einfache
obj instanceof String str
.
Ist es wirklich nicht möglich, den Compiler zu "brechen"?
Möglicherweise so?
if (obj instanceof String str || false) { System.out.println(str.toLowerCase()); }
A.java:8: error: cannot find symbol System.out.println(str.toLowerCase()); ^
Ja! Das sieht schon nach einem Bug aus. Schließlich sind alle drei Bedingungen absolut gleichwertig:
obj instanceof String str
obj instanceof String str && true
obj instanceof String str || false
Flow-Scoping-Regeln sind dagegen eher nicht
trivial , und vielleicht sollte ein solcher Fall wirklich nicht funktionieren. Aber wenn Sie rein aus menschlicher Sicht schauen, dann denke ich, dass dies ein Fehler ist.
Aber komm, lass uns etwas anderes versuchen. Funktioniert das:
if (!(obj instanceof String str)) { throw new RuntimeException(); } System.out.println(str.toLowerCase());
Kompiliert. Das ist gut, da dieser Code dem Folgenden entspricht:
if (!(obj instanceof String str)) { throw new RuntimeException(); } else { System.out.println(str.toLowerCase()); }
Und da beide Optionen gleichwertig sind, erwartet der Programmierer, dass sie auf die gleiche Weise funktionieren.
Was ist mit überlappenden Feldern?
public class A { private String str; public void f(Object obj) { if (obj instanceof String str) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } } }
Der Compiler hat nicht geschworen. Dies ist logisch, da lokale Variablen immer Felder überlappen können. Anscheinend haben sie auch beschlossen, keine Ausnahmen für Musterbindungen zu machen. Andererseits ist ein solcher Code ziemlich zerbrechlich. Ein unvorsichtiger Schritt, und Sie werden möglicherweise nicht bemerken, wie Ihr
if
Zweig gebrochen ist:
private boolean isOK() { return false; } public void f(Object obj) { if (obj instanceof String str || isOK()) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } }
Beide Zweige verwenden jetzt das Feld
str
, das ein unaufmerksamer Programmierer möglicherweise nicht erwartet. Um solche Fehler so früh wie möglich zu erkennen, verwenden Sie die Überprüfungen in der IDE und unterschiedliche Syntaxhervorhebungen für Felder und Variablen. Ich empfehle außerdem, dass Sie
this
Qualifikationsmerkmal immer für Felder verwenden. Dies erhöht die Zuverlässigkeit.
Was ist noch interessant? Wie die "alte"
instanceof
, stimmt die neue nie mit
null
überein. Dies bedeutet, dass Sie sich immer darauf verlassen können, dass Musterbinder niemals
null
:
if (obj instanceof String str) { System.out.println(str.toLowerCase());
Übrigens können Sie mit dieser Eigenschaft solche Ketten verkürzen:
if (a != null) { B b = a.getB(); if (b != null) { C c = b.getC(); if (c != null) { System.out.println(c.getSize()); } } }
Wenn Sie
instanceof
, kann der obige Code folgendermaßen umgeschrieben werden:
if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) { System.out.println(c.getSize()); }
Schreiben Sie in die Kommentare, was Sie über diesen Stil denken. Würden Sie diese Redewendung verwenden?
Was ist mit Generika?
import java.util.List; public class A { public static void main(String[] args) { new A().f(List.of(1, 2, 3)); } public void f(Object obj) { if (obj instanceof List<Integer> list) { System.out.println(list.size()); } } }
> java --enable-preview --source 14 A.java Note: A.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 3
Sehr interessant. Wenn die "alte"
instanceof
nur "
instanceof List
oder "
instanceof List<?>
" Unterstützt, funktioniert die neue mit einem bestimmten Typ. Wir warten darauf, dass die erste Person in eine solche Falle gerät:
if (obj instanceof List<Integer> list) { System.out.println("Int list of size " + list.size()); } else if (obj instanceof List<String> list) { System.out.println("String list of size " + list.size()); }
Warum funktioniert das nicht?Antwort: Mangel an reifizierten Generika in Java.
IMHO, das ist ein ziemlich ernstes Problem. Andererseits weiß ich nicht, wie ich das beheben soll. Es sieht so aus, als müssten Sie sich wieder auf Inspektionen in der IDE verlassen.
Schlussfolgerungen
Im Allgemeinen funktioniert der neue Pattern-Matching-Typ sehr gut. Mit der verbesserten
instanceof
Operators können Sie nicht nur eine Typprüfung durchführen, sondern auch fertige Bindemittel dieses Typs deklarieren, sodass kein manuelles Gießen erforderlich ist. Dies bedeutet, dass der Code weniger Rauschen enthält und der Leser nützliche Logik leichter erkennen kann. Beispielsweise können die meisten
equals()
-Implementierungen in einer Zeile geschrieben werden:
public class Point { private final int x, y; … @Override public int hashCode() { return Objects.hash(x, y); } @Override public boolean equals(Object obj) { return obj instanceof Point p && px == this.x && py == this.y; } }
Der obige Code kann noch kürzer geschrieben werden. Wie?Verwenden von
Einträgen , die auch in Java 14 enthalten sein werden. Wir werden beim nächsten Mal darüber sprechen.
Andererseits werfen einige kontroverse Punkte kleine Fragen auf:
- Nicht vollständig transparente Bereichsregeln (Beispiel mit
instanceof || false
). - Überlappende Felder.
instanceof
und Generika.
Dies sind jedoch eher unbedeutende Anschuldigungen als ernsthafte Behauptungen. Alles in allem sind die enormen Vorteile der neuen
instanceof
Operators definitiv eine zusätzliche Sprache wert. Und wenn es immer noch den Vorschau-Status verlässt und zu einer stabilen Syntax wird, ist es eine gute Motivation, Java 8 endgültig der neuen Java-Version zu überlassen.
PS Ich habe einen
Kanal in Telegram, in dem ich über Java-Nachrichten schreibe. Ich fordere Sie auf, es zu abonnieren.