Компилятор Java не оптимизирует конкатенацию строк

1

Я заметил, что компилятор 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 файла, но результат тот же.

  • 6
    Ты уверен? Может быть, ваш декомпилятор достаточно умен, чтобы восстановить код?
  • 0
    Можете ли вы попробовать: String g = a + b + c; и скажите нам декомпилированный результат?
Показать ещё 3 комментария
Теги:
string
compiler-construction
stringbuilder

3 ответа

2
Лучший ответ

Я сделал 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        

Я думаю, что декомпилятор пытается упростить это, чтобы добиться естественного кодирования.

  • 0
    Спасибо за ваш ответ, и вы правы. Я добавил String h = new StringBuilder(g).append(a).toString(); после компиляции и декомпиляции это выглядело как String str6 = str5 + paramString1;
  • 0
    @ ErcanÖzdemir ах, аккуратный трюк. Я думал, что вам придется сделать javap с вашей версией java.
3

Ваш декомпилятор действительно упрощает код.

Рассмотрим этот исходный файл:

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 если обнаруживает используемый шаблон.

0

Компилятор 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 в вашем коде.

  • 0
    Оптимизация StringBuilder очень часто выполняется компиляторами, а не JIT.
  • 0
    Что, объединяя несколько операторов с использованием + в одно большее выражение? Я никогда не видел, чтобы компилятор делал это, и это явно не произошло ни для OP, ни для Joop Eggen (судя по байт-коду в его ответе).
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню