- PVSM.RU - https://www.pvsm.ru -
И снова здравствуйте. Как мы уже писали, на следующей неделе стартует новая группа обучения по курсу «Разработчик Java» [1], по устоявшейся традиции делимся с вами переводом интересного материала по теме.
Начиная с JDK 9 конкатенация строк претерпела значительные изменения.
JEP 280 [2] («Indify String Concatenation») был реализован в рамках JDK 9 [3] и, в соответствии с разделом «Summary»: «Изменяет статическую последовательность байт-кода конкатенации строк, сгенерированную javac [4], для использования вызовов invokedynamic [5] к функциям библиотеки JDK». Влияние, которое это оказывает на конкатенацию строк в Java, легче всего заметить, посмотрев на javap [6]-вывод классов, использующих конкатенацию строк, которые скомпилированы в JDK до JDK 9 и после JDK 9.
Для первой демонстрации будет использоваться простой Java-класс с именем «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]);
}
}
Ниже показано сопоставление различий для -verbose вывода javap
для метода main(String)
класса HelloWorldStringConcat при компиляции с JDK 8 (AdoptOpenJDK) [7] и JDK 11 (Oracle OpenJDK) [8]. Я выделил несколько ключевых различий.
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
JDK 11 javap-вывод
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
Раздел «Description» в JEP 280 [2] описывает это различие: «Идея состоит в том, чтобы заменить весь танец присоединения StringBuilder простым вызовом invokedynamic
к java.lang.invoke.StringConcatFactory [9], который будет принимать значения, требующие объединения». В этом же разделе показано аналогичное сравнение скомпилированного вывода для аналогичного примера конкатенации строк.
Скомпилированный вывод с JDK 11 для простой конкатенации — это не просто меньшее количество строк, чем в выводе с JDK 8; у него также меньше «дорогих» операций. Потенциальное улучшение производительности может быть достигнуто за счет того, что нет необходимости в обертывании примитивных типов и не требуется создавать множество дополнительных объектов. Одним из основных мотивов этого изменения было «заложить основу для создания оптимизированных обработчиков конкатенации строк, реализуемых без необходимости изменения компилятора Java-to-bytecode» и «включить будущие оптимизации конкатенации строк без дополнительных изменений в байт-коде генерируемом javac. "
Есть интересное следствие этого с точки зрения использования StringBuffer [10] (которому мне в любом случае сложно найти хорошее применение [11]) и StringBuilder [12]. В JEP 280 в “Non-Goal” было заявлено не «вводить какие-либо новые API-интерфейсы для String и/или StringBuilder, которые могли бы помочь в создании более эффективных стратегий перевода». В связи с этим для простой конкатенации строк, такой как в примере в начале этого поста, явное использование StringBuilder и StringBuffer фактически исключает для компилятора возможность использовать фичу, представленную в JEP 280 [2], которую мы обсуждаем в этом посте.
Следующие два листинга показывают аналогичные реализации простого приложения, показанного выше, но вместо конкатенации строк они используют StringBuilder и StringBuffer соответственно. Когда javap -verbose выполняется для этих классов после того, как они скомпилированы с JDK 8 и с JDK 11, в main(String []) методах нет существенных различий.
Явное использование StringBuilder в JDK 8 и JDK 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());
}
}
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
Явное использование StringBuffer в JDK 8 и JDK 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());
}
}
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 и JDK 11 Обработка зацикленных конкатенаций строк
Для последнего примера изменений в JEP 280 в действии я использую пример кода, который может нарушить восприимчивость некоторых разработчиков Java и выполнить конкатенацию строк в цикле. Имейте в виду, что это только иллюстративный пример, и все будет хорошо, но не пытайтесь повторять это дома.
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
В презентации «Enough java.lang.String to Hang Ourselves ...» [13], доктор Хайнц М. Кабуц [14] (Heinz M. Kabutz) и Дмитрий Вязеленко [15] (Dmitry Vyazelenko) обсуждают внесенные изменения в конкатенацию строк Java и кратко их обобщают, “+ больше не компилируется в StringBuilder”. На слайде «Lessons from Today» они заявляют: «Используйте + вместо StringBuilder, где это возможно» и «Перекомпилируйте классы для Java 9+».
Изменения, реализованные в JDK 9 с JEP 280 [2], «позволят в будущем оптимизировать конкатенацию строк, не требуя дополнительных изменений в байт-коде, генерируемом javac». Интересно, что недавно было объявлено, что JEP 348 («Java Compiler Intrinsics for JDK APIs») теперь кандидат в JEP, и его целью является использование аналогичного подхода для компиляции методов String::format [2] и Objects::hash
.
Как считаете, полезная статья? Ждем ваши комментарии и приглашаем всех на день открытых дверей [16] по курсу «Разработчик Java», который уже 25 марта проведет генеральный директор компании ОТУС — Виталий Чибриков [17].
Автор: MaxRokatansky
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/312398
Ссылки в тексте:
[1] «Разработчик Java»: https://otus.pw/vNCk/
[2] JEP 280: https://openjdk.java.net/jeps/280
[3] JDK 9: https://openjdk.java.net/projects/jdk9/
[4] javac: https://docs.oracle.com/en/java/javase/11/tools/javac.html
[5] invokedynamic: https://www.infoq.com/articles/Invokedynamic-Javas-secret-weapon
[6] javap: https://docs.oracle.com/en/java/javase/11/tools/javap.html
[7] (AdoptOpenJDK): https://adoptopenjdk.net/
[8] (Oracle OpenJDK): https://jdk.java.net/11/
[9] java.lang.invoke.StringConcatFactory: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/StringConcatFactory.html
[10] StringBuffer: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StringBuffer.html
[11] сложно найти хорошее применение: http://marxsoftware.blogspot.com/2017/04/stringbuffer-implications.html
[12] StringBuilder: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StringBuilder.html
[13] «Enough java.lang.String to Hang Ourselves ...»: https://www.javaspecialists.eu/talks/pdfs/2018%20Voxxed%20in%20Thessaloniki,%20Greece%20-%20%22Enough%20java.lang.String%20to%20Hang%20Ourselves%20...%22%20by%20Heinz%20Kabutz.pdf
[14] Хайнц М. Кабуц: https://www.javaspecialists.eu/
[15] Дмитрий Вязеленко: https://twitter.com/dvyazelenko?lang=en
[16] день открытых дверей: https://otus.pw/O6bD/
[17] Виталий Чибриков: https://otus.pw/HpOA/
[18] Источник: https://habr.com/ru/post/444822/?utm_campaign=444822
Нажмите здесь для печати.