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, являются их собственными. |