Я заметил, что компилятор Java не конвертирует метод String (+) в метод StringBuilder.append(). Я создал класс, который имеет только один метод
public void doSomething(String a, String b) {
String c = a + "a";
String d = b + "b";
String e = c + d;
String f = e;
System.out.println(f);
}
После компиляции и декомпиляции мой метод выглядел так:
public void doSomething(String paramString1, String paramString2)
{
String str1 = paramString1 + "a";
String str2 = paramString2 + "b";
String str3 = str1 + str2;
String str4 = str3;
System.out.println(str4);
}
Почему компилятор не оптимизирует мой код? Я использую ant для упаковки, а настройка отладки ложна. Я также пробовал javac для одного java файла, но результат тот же.
Я сделал javap -c Test.class
и javap -c Test.class
(Java 8).
public void doSomething(java.lang.String, java.lang.String);
Code:
0: new #2 // class StringBuilder
3: dup
4: invokespecial #3 // Method StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
11: ldc #5 // String a
13: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
16: invokevirtual #6 // Method StringBuilder.toString:()LString;
19: astore_3
20: new #2 // class StringBuilder
23: dup
24: invokespecial #3 // Method StringBuilder."<init>":()V
27: aload_2
28: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
31: ldc #7 // String b
33: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
36: invokevirtual #6 // Method StringBuilder.toString:()LString;
39: astore 4
41: new #2 // class StringBuilder
44: dup
45: invokespecial #3 // Method StringBuilder."<init>":()V
48: aload_3
49: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
52: aload 4
54: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
57: invokevirtual #6 // Method StringBuilder.toString:()LString;
60: astore 5
62: aload 5
64: astore 6
66: getstatic #8 // Field System.out:Ljava/io/PrintStream;
69: aload 6
71: invokevirtual #9 // Method java/io/PrintStream.println:(LString;)V
74: return
Я думаю, что декомпилятор пытается упростить это, чтобы добиться естественного кодирования.
String h = new StringBuilder(g).append(a).toString();
после компиляции и декомпиляции это выглядело как String str6 = str5 + paramString1;
Ваш декомпилятор действительно упрощает код.
Рассмотрим этот исходный файл:
public class foo {
public void a(String[] args) {
String b = (new StringBuilder()).append("x").append(args[0]).toString();
}
public void b(String[] args) {
String b = "x" + args[0];
}
}
Выход javap
:
public class foo {
public foo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void a(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: ldc #4 // String x
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_1
13: iconst_0
14: aaload
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_2
22: return
public void b(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: ldc #4 // String x
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_1
13: iconst_0
14: aaload
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_2
22: return
}
Посмотрите, что байт-коды по существу идентичны: компилятор преобразовал метод b
в метод a
используя оптимизацию StringBuilder.append
.
Теперь посмотрим, что говорит JD:
public class foo
{
public void a(String[] paramArrayOfString)
{
String str = "x" + paramArrayOfString[0];
}
public void b(String[] paramArrayOfString) {
String str = "x" + paramArrayOfString[0];
}
}
Это право: JD фактически принимает функцию a
и интерпретирует ее как добавление строки, хотя исходный исходный код был указан с использованием явного StringBuilder
!
Таким образом, мы видим, что JD попытается изменить оптимизацию StringBuilder
если обнаруживает используемый шаблон.
Компилятор Java не выполняет оптимизацию во время компиляции, поскольку он остался для JIT во время выполнения. Если вы хотите эффективно создать строку для нескольких операторов, вы должны использовать StringBuilder
.
Оператор +
компилируется в вызовы объекта StringBuilder
. Когда вы пишете:
String c = a + "a";
компилятор создает код так, как если бы вы написали:
String c = new StringBuilder().append(a).append("a").toString();
Если вы используете +
несколько раз в одном выражении, таком как a + "a" + b + "b"
, компилятор будет использовать один StringBuilder
для всего выражения, призывая append
столько раз, сколько необходимо. Но он не оптимизирует несколько операторов в одном эквивалентном выражении, поэтому, если вы хотите использовать один StringBuilder
для объединения всех ваших строк вместе, вам нужно либо записать его как одно выражение, либо явно использовать StringBuilder
в вашем коде.
+
в одно большее выражение? Я никогда не видел, чтобы компилятор делал это, и это явно не произошло ни для OP, ни для Joop Eggen (судя по байт-коду в его ответе).
String g = a + b + c;
и скажите нам декомпилированный результат?