JDK 9 / JEP 280: la concaténation de chaînes ne sera plus jamais la même

Bonjour encore. Comme nous l'avons déjà écrit, la semaine prochaine commencera un nouveau groupe de formation sur le cours "Java Developer" , selon la tradition établie, nous partagerons avec vous la traduction de matériel intéressant sur le sujet.

Depuis JDK 9, la concaténation de chaînes a subi des changements importants.

JEP 280 («Indify String Concatenation») a été implémenté dans le cadre de JDK 9 et, selon la section Summary: «Change la séquence de bytecode de concaténation de chaînes générée par javac pour utiliser des appels dynamiques invoqués aux fonctions de bibliothèque JDK.» L'effet que cela a sur la concaténation de chaînes en Java est plus facilement remarqué en regardant la sortie javap des classes qui utilisent la concaténation de chaînes qui ont été compilées dans le JDK avant JDK 9 et après JDK 9.



Pour la première démonstration, une simple classe Java appelée «HelloWorldStringConcat» sera utilisée.

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

Voici une comparaison des différences de sortie javap -verbose pour la méthode main(String) de la classe HelloWorldStringConcat lors de la compilation avec JDK 8 (AdoptOpenJDK) et JDK 11 (Oracle OpenJDK) . J'ai souligné quelques différences clés.

Sortie JDK 8 javap

 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 

Sortie javap JDK 11

 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 

La section "Description" dans JEP 280 décrit cette différence: "L'idée est de remplacer la danse de jointure StringBuilder entière par un simple appel invokedynamic à java.lang.invoke.StringConcatFactory , qui prendra des valeurs qui doivent être fusionnées." La même section montre une comparaison similaire de la sortie compilée pour un exemple de concaténation de chaînes similaire.

La sortie compilée avec JDK 11 pour une concaténation facile n'est pas seulement moins de lignes que la sortie de JDK 8; il a également moins d'opérations "coûteuses". Des améliorations potentielles des performances peuvent être obtenues en raison du fait qu'il n'est pas nécessaire d'encapsuler les types primitifs et que vous n'avez pas besoin de créer de nombreux objets supplémentaires. L'un des principaux motifs de ce changement était de «jeter les bases de la création de gestionnaires de concaténation de chaînes optimisés qui sont mis en œuvre sans avoir besoin de changer le compilateur Java en bytecode» et «d'activer les futures optimisations de concaténation de chaînes sans modifications supplémentaires du bytecode généré par javac. "

Il y a une conséquence intéressante à cela en termes d'utilisation de StringBuffer (pour lequel j'ai du mal à trouver une bonne utilisation de toute façon) et StringBuilder . Dans JEP 280, «Non-Goal» a déclaré ne pas «introduire de nouvelles API pour String et / ou StringBuilder qui pourraient aider à créer des stratégies de traduction plus efficaces». À cet égard, pour la concaténation de chaînes simple, comme dans l'exemple au début de cet article, l'utilisation explicite de StringBuilder et StringBuffer exclut virtuellement le compilateur d'utiliser la fonctionnalité introduite dans JEP 280 , dont nous discutons dans cet article.

Les deux listes suivantes montrent des implémentations similaires de l'application simple présentée ci-dessus, mais au lieu de concaténer des chaînes, elles utilisent respectivement StringBuilder et StringBuffer. Lorsque javap -verbose est exécuté pour ces classes après leur compilation avec JDK 8 et JDK 11, il n'y a pas de différences significatives dans les méthodes principales (String []).

L'utilisation explicite de StringBuilder dans JDK 8 et JDK 11 est la même

 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 

L'utilisation explicite de StringBuffer dans JDK 8 et JDK 11 est la même

 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 et JDK 11 Gestion des concaténations de chaînes en boucle

Pour le dernier exemple de modifications apportées à JEP 280 en action, j'utilise un exemple de code qui peut briser la sensibilité de certains développeurs Java et concaténer des chaînes en boucle. Gardez à l'esprit qu'il ne s'agit que d'un exemple illustratif et que tout ira bien, mais n'essayez pas de le répéter à la maison.

 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 

Dans la présentation «Assez java.lang.String pour se suspendre ...» , le Dr Heinz M. Kabutz et Dmitry Vyazelenko discutent des modifications apportées à la concaténation de chaînes Java et les résument brièvement, «+ plus non compilé dans StringBuilder ». Sur la diapositive Lessons from Today, ils indiquent: «Utilisez + au lieu de StringBuilder si possible» et «Recompiler les classes pour Java 9+».

Les modifications implémentées dans JDK 9 avec JEP 280 , "permettront à l'avenir d'optimiser la concaténation des chaînes, sans nécessiter de modifications supplémentaires dans le bytecode généré par javac." Fait intéressant, il a été récemment annoncé que JEP 348 («Java Compiler Intrinsics for JDK APIs») est désormais un candidat pour JEP, et son objectif est d'utiliser une approche similaire pour compiler Objects::hash méthodes String :: format et Objects::hash .

Selon vous, qu'est-ce qu'un article utile? Nous attendons vos commentaires et invitons tout le monde à une journée portes ouvertes au cours Java Developer, qui sera organisé le 25 mars par le directeur général d'OTUS - Vitaly Chibrikov .

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


All Articles