JDK 9 / JEP 280: la concatenación de cadenas nunca volverá a ser la misma

Hola de nuevo Como ya escribimos, la próxima semana comenzará un nuevo grupo de capacitación en el curso "Desarrollador Java" , de acuerdo con la tradición establecida, compartiremos con ustedes la traducción de material interesante sobre el tema.

Comenzando con JDK 9, la concatenación de cadenas ha experimentado cambios significativos.

JEP 280 ("Indicar concatenación de cadenas") se implementó como parte de JDK 9 y, de acuerdo con la sección Resumen: "Cambia la secuencia de bytecode de concatenación de cadenas generada por javac para usar llamadas dinámicas invocadas a las funciones de la biblioteca JDK". El efecto que esto tiene en la concatenación de cadenas en Java se nota más fácilmente al observar la salida de Java de las clases que usan la concatenación de cadenas que se compilaron en JDK antes de JDK 9 y después de JDK 9.



Para la primera demostración, se utilizará una clase simple de Java llamada "HelloWorldStringConcat".

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

La siguiente es una comparación de las diferencias para la salida -verbose javap para el método main(String) de la clase HelloWorldStringConcat cuando se compila con JDK 8 (AdoptOpenJDK) y JDK 11 (Oracle OpenJDK) . Destaqué algunas diferencias clave.

Salida javap JDK 8

 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 

Salida 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 sección "Descripción" en JEP 280 describe esta diferencia: "La idea es reemplazar todo el baile de unión StringBuilder con una simple llamada invokedynamic a java.lang.invoke.StringConcatFactory , que tomará valores que deben fusionarse". La misma sección muestra una comparación similar de la salida compilada para un ejemplo de concatenación de cadenas similar.

La salida compilada con JDK 11 para una concatenación fácil no es solo menos líneas que la salida de JDK 8; él también tiene menos operaciones "caras". Se pueden lograr mejoras de rendimiento potenciales debido al hecho de que no es necesario ajustar tipos primitivos y no es necesario crear muchos objetos adicionales. Uno de los motivos principales de este cambio fue "sentar las bases para crear controladores de concatenación de cadenas optimizados que se implementen sin la necesidad de cambiar el compilador de código de bytes a Java" y "habilitar futuras optimizaciones de concatenación de cadenas sin cambios adicionales en el código de bytes generado por javac. "

Hay una consecuencia interesante de esto en términos del uso de StringBuffer (que de todos modos me resulta difícil encontrar un buen uso ) y StringBuilder . En JEP 280 en "No objetivo", se dijo que no "introduciría ninguna API nueva para String y / o StringBuilder que pudiera ayudar a crear estrategias de traducción más eficientes". En este sentido, para la concatenación de cadenas simple, como en el ejemplo al principio de esta publicación, el uso explícito de StringBuilder y StringBuffer prácticamente excluye al compilador del uso de la característica introducida en JEP 280 , que discutimos en esta publicación.

Los siguientes dos listados muestran implementaciones similares de la aplicación simple que se muestra arriba, pero en lugar de concatenar cadenas, usan StringBuilder y StringBuffer respectivamente. Cuando se ejecuta javap -verbose para estas clases después de que se compilan con JDK 8 y con JDK 11, no hay diferencias significativas en los métodos principales (String []).

El uso explícito de StringBuilder en JDK 8 y JDK 11 es el mismo

 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 

El uso explícito de StringBuffer en JDK 8 y JDK 11 es el mismo

 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 y JDK 11 Manejo de concatenaciones de cadenas en bucle

Para el último ejemplo de cambios en JEP 280 en acción, uso un código de muestra que puede romper la susceptibilidad de algunos desarrolladores de Java y concatenar cadenas en un bucle. Tenga en cuenta que este es solo un ejemplo ilustrativo y que todo estará bien, pero no intente repetirlo en casa.

 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 

En la presentación "Suficiente java.lang.String to Hang Ourselves ..." , el Dr. Heinz M. Kabutz y Dmitry Vyazelenko discuten los cambios realizados en la concatenación de cadenas Java y los resumen brevemente, "+ más no compilado en StringBuilder ". En la diapositiva Lecciones del día de hoy, dicen: "Use + en lugar de StringBuilder donde sea posible" y "Recompile clases para Java 9+".

Los cambios implementados en JDK 9 con JEP 280 , "permitirán en el futuro optimizar la concatenación de cadenas, sin requerir cambios adicionales en el bytecode generado por javac". Curiosamente, se anunció recientemente que JEP 348 ("Java Compiler Intrinsics for JDK APIs") ahora es un candidato para JEP, y su objetivo es utilizar un enfoque similar para compilar los métodos String :: format y Objects::hash .

¿Cuál crees que es un artículo útil? Estamos a la espera de sus comentarios e invitamos a todos a una jornada de puertas abiertas en el curso Java Developer, que se llevará a cabo el 25 de marzo por el director general de OTUS - Vitaly Chibrikov .

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


All Articles