JDK 9 / JEP 280: a concatenação de cadeias nunca mais será a mesma

Olá novamente. Como já escrevemos, na próxima semana um novo grupo de treinamento no curso "Java Developer" começará, de acordo com a tradição estabelecida, compartilharemos com você a tradução de material interessante sobre o tema.

A partir do JDK 9, a concatenação de cadeias passou por mudanças significativas.

O JEP 280 (“Indify Concatenation String”) foi implementado como parte do JDK 9 e, de acordo com a seção Summary: “Altera a sequência do bytecode da concatenação da string gerada por javac para usar chamadas dinâmicas invocadas para funções da biblioteca JDK.” O efeito que isso tem na concatenação de cadeias de caracteres em Java é mais facilmente percebido observando a saída javap de classes que usam concatenação de cadeias que foram compiladas no JDK antes do JDK 9 e depois do JDK 9.



Para a primeira demonstração, uma classe Java simples chamada “HelloWorldStringConcat” será usada.

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

A seguir, é apresentada uma comparação das diferenças da saída -verbose javap para o método main(String) da classe HelloWorldStringConcat ao compilar com o JDK 8 (AdoptOpenJDK) e o JDK 11 (Oracle OpenJDK) . Eu destaquei algumas diferenças importantes.

Saída 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 

Saída 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 

A seção "Descrição" no JEP 280 descreve esta diferença: "A idéia é substituir toda a dança de junção StringBuilder por uma chamada invokedynamic simples para java.lang.invoke.StringConcatFactory , que assumirá valores que precisam ser mesclados". A mesma seção mostra uma comparação semelhante da saída compilada para um exemplo de concatenação de string semelhante.

A saída compilada com o JDK 11 para concatenação fácil não é apenas menos linhas do que a saída do JDK 8; ele também tem menos operações "caras". Potenciais aprimoramentos de desempenho podem ser alcançados devido ao fato de que não há necessidade de agrupar tipos primitivos e você não precisa criar muitos objetos adicionais. Um dos principais motivos dessa mudança foi "estabelecer as bases para a criação de manipuladores de concatenação de string otimizados que são implementados sem a necessidade de alterar o compilador Java para bytecode" e "ativar otimizações futuras de concatenação de string sem alterações adicionais no bytecode gerado pelo javac. "

Há uma conseqüência interessante disso em termos de uso de StringBuffer (que acho difícil encontrar um bom uso para qualquer maneira) e StringBuilder . No JEP 280, “Non-Goal” declarou não “introduzir novas APIs para String e / ou StringBuilder que poderiam ajudar a criar estratégias de tradução mais eficientes”. Nesse sentido, para concatenação simples de strings, como no exemplo no início deste post, o uso explícito de StringBuilder e StringBuffer praticamente exclui o compilador de usar o recurso introduzido no JEP 280 , que discutiremos neste post.

As duas listagens a seguir mostram implementações semelhantes do aplicativo simples mostrado acima, mas em vez de concatenar cadeias, elas usam StringBuilder e StringBuffer, respectivamente. Quando o javap -verbose é executado para essas classes após serem compiladas com o JDK 8 e com o JDK 11, não há diferenças significativas nos métodos principais (String []).

O uso explícito de StringBuilder no JDK 8 e JDK 11 é o mesmo

 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 

O uso explícito de StringBuffer no JDK 8 e JDK 11 é o mesmo

 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 e JDK 11 manipulando concatenações de seqüência de caracteres em loop

Para o exemplo final de alterações no JEP 280 em ação, uso um código de exemplo que pode quebrar a suscetibilidade de alguns desenvolvedores Java e concatenar seqüências de caracteres em um loop. Lembre-se de que este é apenas um exemplo ilustrativo e tudo ficará bem, mas não tente repeti-lo em 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 

Na apresentação “Chega de java.lang.String para se enforcar ...” , o Dr. Heinz M. Kabutz e Dmitry Vyazelenko discutem as alterações feitas na concatenação de strings Java e as resumem brevemente, “+ more não compilado no StringBuilder ". No slide Lessons from Today, eles afirmam: “Use + em vez de StringBuilder sempre que possível” e “Recompile classes para Java 9+.”

As alterações implementadas no JDK 9 com o JEP 280 , "permitirão otimizar a concatenação de cadeias no futuro, sem exigir alterações adicionais no bytecode gerado pelo javac". Curiosamente, foi anunciado recentemente que o JEP 348 (“Java Compiler Intrinsics for JDK APIs”) agora é candidato ao JEP, e seu objetivo é usar uma abordagem semelhante para compilar os métodos String :: format e Objects::hash .

O que você acha que é um artigo útil? Estamos aguardando seus comentários e convidamos todos para um dia aberto no curso Java Developer, que será realizado em 25 de março pelo diretor geral da OTUS - Vitaly Chibrikov .

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


All Articles