JDK 9 / JEP 280: Die Verkettung von Zeichenfolgen wird nie wieder dieselbe sein

Hallo nochmal. Wie wir bereits geschrieben haben, wird nächste Woche eine neue Gruppe von Schulungen zum Kurs "Java Developer" beginnen. Gemäß der etablierten Tradition werden wir Ihnen die Übersetzung von interessantem Material zu diesem Thema mitteilen.

Ab JDK 9 hat sich die Verkettung von Zeichenfolgen erheblich geändert.

JEP 280 ("Indify String Concatenation") wurde als Teil von JDK 9 implementiert und laut Abschnitt "Zusammenfassung": "Ändert die von Javac generierte Bytecode-Sequenz für die String-Verkettung, um aufgerufene dynamische Aufrufe von JDK-Bibliotheksfunktionen zu verwenden." Die Auswirkung auf die Zeichenfolgenverkettung in Java lässt sich am einfachsten anhand der Javap- Ausgabe von Klassen feststellen , die die Zeichenfolgenverkettung verwenden, die im JDK vor JDK 9 und nach JDK 9 kompiliert wurden.



Für die erste Demonstration wird eine einfache Java-Klasse namens "HelloWorldStringConcat" verwendet.

package dustin.examples; import static java.lang.System.out; public class HelloWorldStringConcat { public static void main(final String[] arguments) { out.println("Hello, " + arguments[0]); } } 

Das Folgende ist ein Vergleich der Unterschiede für die -verbose-Javap-Ausgabe für die main(String) -Methode der HelloWorldStringConcat-Klasse beim Kompilieren mit JDK 8 (AdoptOpenJDK) und JDK 11 (Oracle OpenJDK) . Ich habe einige wichtige Unterschiede hervorgehoben.

JDK 8 Javap-Ausgabe

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class Last modified Jan 28, 2019; size 625 bytes MD5 checksum 3e270bafc795b47dbc2d42a41c8956af Compiled from "HelloWorldStringConcat.java" public class dustin.examples.HelloWorldStringConcat minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload ":()V 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return //  java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

JDK 11 Javap-Ausgabe

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class Last modified Jan 28, 2019; size 908 bytes MD5 checksum 0e20fe09f6967ba96124abca10d3e36d Compiled from "HelloWorldStringConcat.java" public class dustin.examples.HelloWorldStringConcat minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: iconst_0 5: aaload 6: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String; 11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: return 

Der Abschnitt "Beschreibung" in JEP 280 beschreibt diesen Unterschied: "Die Idee ist, den gesamten StringBuilder-Join-Tanz durch einen einfachen invokedynamic Aufruf von java.lang.invoke.StringConcatFactory zu ersetzen, der Werte annimmt, die zusammengeführt werden müssen." Der gleiche Abschnitt zeigt einen ähnlichen Vergleich der kompilierten Ausgabe für ein ähnliches Beispiel für die Verkettung von Zeichenfolgen.

Die kompilierte Ausgabe mit JDK 11 zur einfachen Verkettung umfasst nicht nur weniger Zeilen als die Ausgabe von JDK 8. Er hat auch weniger "teure" Operationen. Potenzielle Leistungsverbesserungen können erzielt werden, da primitive Typen nicht umbrochen werden müssen und nicht viele zusätzliche Objekte erstellt werden müssen. Eines der Hauptmotive für diese Änderung war, "die Grundlage für die Erstellung optimierter Handler für die Verkettung von Zeichenfolgen zu legen, die implementiert werden, ohne dass der Java-zu-Bytecode-Compiler geändert werden muss" und "zukünftige Optimierungen für die Verkettung von Zeichenfolgen ohne zusätzliche Änderungen des von javac generierten Bytecodes zu ermöglichen". ""

Dies hat eine interessante Konsequenz in Bezug auf die Verwendung von StringBuffer (für den ich ohnehin nur schwer eine gute Verwendung finde ) und StringBuilder . In JEP 280 in "Non-Goal" wurde angegeben, keine "neuen APIs für String und / oder StringBuilder einzuführen, die zur Erstellung effizienterer Übersetzungsstrategien beitragen könnten". In dieser Hinsicht schließt die explizite Verwendung von StringBuilder und StringBuffer für die einfache Verkettung von Zeichenfolgen, wie im Beispiel am Anfang dieses Beitrags, den Compiler praktisch von der Verwendung der in JEP 280 eingeführten Funktion aus , die wir in diesem Beitrag diskutieren.

Die folgenden beiden Auflistungen zeigen ähnliche Implementierungen der oben gezeigten einfachen Anwendung, aber anstatt Zeichenfolgen zu verketten, verwenden sie StringBuilder bzw. StringBuffer. Wenn javap -verbose für diese Klassen ausgeführt wird, nachdem sie mit JDK 8 und mit JDK 11 kompiliert wurden, gibt es keine signifikanten Unterschiede in den Hauptmethoden (String []).

Die explizite Verwendung von StringBuilder in JDK 8 und JDK 11 ist dieselbe

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringBuilder { public static void main(final String[] arguments) { out.println(new StringBuilder().append("Hello, ").append(arguments[0]).toString()); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class Last modified Jan 28, 2019; size 627 bytes MD5 checksum e7acc3bf0ff5220ba5142aed7a34070f Compiled from "HelloWorldStringBuilder.java" public class dustin.examples.HelloWorldStringBuilder minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; //  java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder //  java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class Last modified Jan 28, 2019; size 627 bytes MD5 checksum d04ee3735ce98eb6237885fac86620b4 Compiled from "HelloWorldStringBuilder.java" public class dustin.examples.HelloWorldStringBuilder minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

Die explizite Verwendung von StringBuffer in JDK 8 und JDK 11 ist dieselbe

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringBuffer { public static void main(final String[] arguments) { out.println(new StringBuffer().append("Hello, ").append(arguments[0]).toString()); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class Last modified Jan 28, 2019; size 623 bytes MD5 checksum fdfb90497db6a3494289f2866b9a3a8b Compiled from "HelloWorldStringBuffer.java" public class dustin.examples.HelloWorldStringBuffer minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuffer 6: dup 7: invokespecial #4 // Method java/lang/StringBuffer."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: invokevirtual #7 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class Last modified Jan 28, 2019; size 623 bytes MD5 checksum e4a83b6bb799fd5478a65bc43e9af437 Compiled from "HelloWorldStringBuffer.java" public class dustin.examples.HelloWorldStringBuffer minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuffer 6: dup 7: invokespecial #4 // Method java/lang/StringBuffer."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: invokevirtual #7 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

JDK 8 und JDK 11 Umgang mit verketteten String-Verkettungen

Für das letzte Beispiel für Änderungen an JEP 280 in Aktion verwende ich Beispielcode, der die Anfälligkeit einiger Java-Entwickler aufheben und Zeichenfolgen in einer Schleife verketten kann. Denken Sie daran, dass dies nur ein anschauliches Beispiel ist und alles in Ordnung ist, aber versuchen Sie nicht, es zu Hause zu wiederholen.

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringConcatComplex { public static void main(final String[] arguments) { String message = "Hello"; for (int i=0; i<25; i++) { message += i; } out.println(message); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class Last modified Jan 30, 2019; size 766 bytes MD5 checksum 772c4a283c812d49451b5b756aef55f1 Compiled from "HelloWorldStringConcatComplex.java" public class dustin.examples.HelloWorldStringConcatComplex minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String Hello 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 25 8: if_icmpge 36 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: iload_2 23: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 26: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: astore_1 30: iinc 2, 1 ":()V 18: aload_1 33: goto 5 36: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 39: aload_1 40: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 43: return 

  Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class Last modified Jan 30, 2019; size 1018 bytes MD5 checksum 967fef3e7625965ef060a831edb2a874 Compiled from "HelloWorldStringConcatComplex.java" public class dustin.examples.HelloWorldStringConcatComplex minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String Hello 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 25 8: if_icmpge 25 11: aload_1 12: iload_2 13: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String; 18: astore_1 19: iinc 2, 1 22: goto 5 25: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_1 29: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: return 

In der Präsentation „Genug java.lang.String, um uns aufzuhängen ...“ diskutieren Dr. Heinz M. Kabutz und Dmitry Vyazelenko die Änderungen an der Java-String-Verkettung und fassen sie kurz zusammen: „+ more nicht in StringBuilder kompiliert “. Auf der Folie "Lessons from Today" heißt es: "Verwenden Sie nach Möglichkeit + anstelle von StringBuilder" und "Klassen für Java 9+ neu kompilieren".

Die in JDK 9 mit JEP 280 implementierten Änderungen "werden es in Zukunft ermöglichen, die Verkettung von Zeichenfolgen zu optimieren, ohne dass zusätzliche Änderungen des von javac generierten Bytecodes erforderlich sind." Interessanterweise wurde kürzlich bekannt gegeben, dass JEP 348 („Java Compiler Intrinsics for JDK APIs“) jetzt ein Kandidat für JEP ist. Ziel ist es, einen ähnlichen Ansatz zum Kompilieren der Methoden String :: format und Objects::hash verwenden.

Was halten Sie für einen nützlichen Artikel? Wir warten auf Ihre Kommentare und laden alle zu einem Tag der offenen Tür zum Java Developer-Kurs ein, der am 25. März vom Generaldirektor von OTUS - Vitaly Chibrikov abgehalten wird .

Source: https://habr.com/ru/post/de444822/


All Articles