StringBuilder vs concatenação em toString(): como funciona por baixo dos panos
\n\nVocê já se perguntou por que no Stack Overflow ainda há debates acalorados sobre como escrever toString() corretamente em Java? Alguns desenvolvedores acreditam piamente que StringBuilder é o único caminho correto. Outros usam tranquilamente a concatenação simples com o sinal de mais e não se preocupam. Quem está certo?
Vamos dar uma olhada por baixo dos panos do compilador Java e do otimizador JIT (Just-In-Time). Você descobrirá o que realmente acontece quando escreve str1 + str2 e por que em 2024 a resposta para essa pergunta pode surpreendê-lo.
Por que este tópico é tão popular?
\n\nA pergunta "StringBuilder vs String concatenation in toString()" acumulou milhares de visualizações no Stack Overflow não por acaso. Todo desenvolvedor Java já escreveu o método toString() pelo menos uma vez. E todos já enfrentaram a escolha: escrever de forma elegante com o sinal de mais ou "corretamente" com StringBuilder.
O mito de que a concatenação de strings em Java é sempre lenta existe há mais de 20 anos. E era verdade... na era do Java 1.4 e Java 5. Mas os compiladores não pararam no tempo.
\n\nO que diz a especificação Java?
\n\nDe acordo com a especificação Java Language Specification (JLS §15.18.1), o operador + para strings é açúcar sintático. O compilador é obrigado a transformar a concatenação de strings em código eficiente. A partir do Java 5 (J2SE 5.0), o compilador javac substitui automaticamente a concatenação pelo uso de StringBuilder.
Vamos verificar isso na prática.
\n\nExemplo de código: um toString() simples
\n\nVamos pegar um exemplo clássico — o método toString() para a classe Person:
public class Person {\n private String name;\n private int age;\n private String email;\n\n @Override\n public String toString() {\n return "Person{" +\n "name='" + name + '\\'' +\n ", age=" + age +\n ", email='" + email + '\\'' +\n '}';\n }\n}\n\n\nParece limpo e legível. Mas como fica no bytecode?
\n\nOlhando o bytecode
\n\nVamos compilar esta classe e examinar o bytecode com javap -c Person.class:
public java.lang.String toString();\n Code:\n 0: new #7 // class java/lang/StringBuilder\n 3: dup\n 4: invokespecial #8 // Method StringBuilder."<init>":()V\n 7: ldc #9 // String "Person{name='"\n 9: invokevirtual #10 // StringBuilder.append(String)\n 12: aload_0\n 13: getfield #3 // Field name:Ljava/lang/String;\n 16: invokevirtual #10 // StringBuilder.append(String)\n 19: ldc #11 // String "', age="\n 21: invokevirtual #10 // StringBuilder.append(String)\n 24: aload_0\n 25: getfield #4 // Field age:I\n 28: invokevirtual #12 // StringBuilder.append(int)\n 31: ldc #13 // String ", email='"\n 33: invokevirtual #10 // StringBuilder.append(String)\n 36: aload_0\n 37: getfield #5 // Field email:Ljava/lang/String;\n 40: invokevirtual #10 // StringBuilder.append(String)\n 43: ldc #14 // String "'}"\n 45: invokevirtual #10 // StringBuilder.append(String)\n 48: invokevirtual #15 // StringBuilder.toString():String\n 51: areturn\n\n\nViu? O compilador criou o StringBuilder sozinho! Ele cria um novo objeto StringBuilder, chama append() para cada parte da string e depois toString(). A mesma coisa que você escreveria manualmente, mas com um código-fonte mais legível.
E quanto ao StringBuilder explícito?
\n\nAgora vamos escrever a versão com StringBuilder explícito:
@Override\npublic String toString() {\n Strin