JEP 280 («Indify String Concatenation») был реализован совместно с JDK 9 и, согласно его разделу «Summary», «Измените [s] последовательность статических байт-кодов String
-concatenation, сгенерированную javac, для использования вызываемых динамических вызовов функций библиотеки JDK. » Влияние, которое это оказывает на конкатенацию строк в Java, легче всего увидеть, посмотрев выходные данные классов javap с использованием конкатенации строк, скомпилированных в JDK до JDK 9 и после JDK 9.
Следующий простой Java-класс с именем «HelloWorldStringConcat» будет использоваться для первой демонстрации.
01
02
03
04
05
06
07
08
09
10
11
|
package dustin.examples; import static java.lang.System.out; public class HelloWorldStringConcat { public static void main( final String[] arguments) { out.println( "Hello, " + arguments[ 0 ]); } } |
Контрастность различий в выводе javap
из javap
для метода main(String)
класса HelloWorldStringConcat
при компиляции с JDK 8 ( AdoptOpenJDK ) и JDK 11 ( Oracle OpenJDK ) показана ниже. Я выделил некоторые ключевые различия.
JDK 8 выход javap
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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."":()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 |
JDK 11 выход javap
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
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 |
Раздел «Описание» в JEP 280 описывает это различие: «Идея состоит в том, чтобы заменить весь танец добавления invokedynamic
простым invokedynamic
вызова java.lang.invoke.StringConcatFactory , который будет принимать значения, требующие объединения». В этом же разделе показано аналогичное сравнение скомпилированного вывода для аналогичного примера конкатенации строк.
Скомпилированный вывод из JDK 11 для простой конкатенации строк — это не просто меньше строк, чем его аналог JDK 8; у него также меньше «дорогих» операций. Потенциальное улучшение производительности может быть достигнуто за счет того, что нет необходимости в обертывании примитивных типов и не нужно создавать множество дополнительных объектов. Одним из основных мотивов этого изменения было «заложить основу для создания оптимизированных обработчиков конкатенации строк, реализуемых без необходимости изменения компилятора Java-to-bytecode» и «включить будущие оптимизации конкатенации строк без необходимости внесения дополнительных изменений в байт-код испускается javac
. »
JEP 280 не влияет на StringBuilder
или StringBuffer
Есть интересное следствие этого с точки зрения использования StringBuffer ( в любом случае мне трудно найти хорошее применение ) и StringBuilder . В JEP 280 было заявлено, что «нецелевой» не «вводить какие-либо новые API-интерфейсы String
и / или StringBuilder
которые могли бы помочь в создании более эффективных стратегий перевода». В связи с этим для простых конкатенаций строк, подобных показанным в примере кода в начале этого поста, явное использование StringBuilder
и StringBuffer
фактически исключает возможность для компилятора использовать функцию, введенную в JEP 280, которая обсуждалась в этом посте. ,
Следующие два листинга кода показывают аналогичные реализации для простого приложения, показанного выше, но они используют StringBuilder
и StringBuffer
соответственно вместо конкатенации строк. Когда javap -verbose
выполняется для этих классов после того, как они скомпилированы с JDK 8 и с JDK 11, в main(String[])
методах нет существенных различий.
Явное использование StringBuilder
в JDK 8 и JDK 11 одинаково
01
02
03
04
05
06
07
08
09
10
11
|
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()); } } |
JDK 8 вывод javap
для HelloWorldStringBuilder.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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; 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 |
JDK 11 выходных данных javap
для HelloWorldStringBuilder.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 |
Явное использование StringBuffer
в JDK 8 и JDK 11 одинаково
01
02
03
04
05
06
07
08
09
10
11
|
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()); } } |
JDK 8 вывод javap
для HelloWorldStringBuffer.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 |
JDK 11 вывод javap
для HelloWorldStringBuffer.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 и JDK 11 Обработка конкатенации петлевых строк
Для моего последнего примера изменений в действии JEP 280 я использую пример кода, который может нарушить восприимчивость некоторых разработчиков Java и выполнить конкатенацию строк в цикле. Имейте в виду, что это только иллюстративный пример, и все будет хорошо, но не пытайтесь делать это дома.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
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); } } |
JDK 8 вывод javap
для HelloWorldStringConcatComplex.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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."":()V 18 : aload_1 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 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 |
Вывод javap
JDK 11 для HelloWorldStringConcatComplex.main(String[])
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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 |
В презентации « Достаточно java.lang.String, чтобы повеситься … », доктор Хайнц М. Кабуц и Дмитрий Вязеленко обсуждают внесенные в JEP 280 изменения в конкатенацию строк Java и кратко их обобщают: « +
больше не компилируется в StringBuilder
. » На слайде «Уроки сегодняшнего дня» они заявляют: «Используйте +
вместо StringBuilder
где это возможно» и «перекомпилируйте классы для Java 9+».
Изменения, реализованные в JDK 9 для JEP 280, «позволят в будущем оптимизировать конкатенацию строк, не требуя дополнительных изменений в байт-коде, испускаемом javac
». Интересно, что недавно было объявлено, что JEP 348 («Встроенные функции Java-компилятора для API JDK») теперь является кандидатом в JEP, и его целью является использование аналогичного подхода для компиляции методов String::format
и Objects::hash
.
Опубликовано на Java Code Geeks с разрешения Дастина Маркса, партнера нашей программы JCG . См. Оригинальную статью здесь: JDK 9 / JEP 280: конкатенации строк никогда не будут прежними Мнения, высказанные участниками Java Code Geeks, являются их собственными. |