StringBuilder vs concatenación en toString(): cómo funciona bajo el capó
\n\n¿Alguna vez te has preguntado por qué en Stack Overflow todavía hay debates sobre cómo escribir correctamente toString() en Java? Algunos desarrolladores creen firmemente que StringBuilder es el único camino correcto. Otros usan tranquilamente la concatenación simple con el signo más y no se preocupan. ¿Quién tiene la razón?
Echemos un vistazo bajo el capó del compilador de Java y del optimizador JIT (Just-In-Time). Descubrirá lo que realmente sucede cuando escribe str1 + str2 y por qué en 2024 la respuesta a esta pregunta podría sorprenderle.
¿Por qué este tema es tan popular?
\n\nLa pregunta "StringBuilder vs String concatenation in toString()" ha acumulado miles de visitas en Stack Overflow por una razón. Cada desarrollador de Java ha escrito el método toString() al menos una vez. Y todos se han enfrentado a la elección: escribir de forma elegante con el signo más o "correctamente" con StringBuilder.
El mito de que la concatenación de cadenas en Java siempre es lenta ha existido durante más de 20 años. Y era cierto... en la era de Java 1.4 y Java 5. Pero los compiladores no se quedan quietos.
\n\n¿Qué dice la especificación de Java?
\n\nSegún la especificación del lenguaje Java (JLS §15.18.1), el operador + para cadenas es azúcar sintáctico. El compilador está obligado a transformar la concatenación de cadenas en código eficiente. Desde Java 5 (J2SE 5.0), el compilador javac reemplaza automáticamente la concatenación con el uso de StringBuilder.
Verifiquemos esto en la práctica.
\n\nEjemplo de código: un toString() simple
\n\nTomemos un ejemplo clásico: el método toString() para la clase 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\nSe ve limpio y legible. Pero, ¿cómo se ve en bytecode?
\n\nEchando un vistazo al bytecode
\n\nCompilemos esta clase y veamos el bytecode con 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\n¿Lo ves? ¡El compilador creó su propio StringBuilder! Crea un nuevo objeto StringBuilder, llama a append() para cada parte de la cadena y luego a toString(). Es lo mismo que habrías escrito manualmente, pero con un código fuente más legible.
¿Y qué pasa con un StringBuilder explícito?
\n\nAhora escribamos la versión con un StringBuilder explícito:
@Override\npublic String toString() {\n Strin