Skip to content

Java 101 – String y StringBuilder

JM Java 101 – Concatenación (o como utilizar cadenas para hacer una gran cadena)

Hay una teoría bien difundida que dice no se debe de concatenar utilizando el símbolo de + pues este causa que varios objetos String temporales sean creados. Se dice en algunos sitios que en su lugar se debe utilizar la clase StringBuilder.

¿Qué tan cierto es esto?

He creído conveniente comprobarlo después de yo mismo pensar esto durante algun tiempo. Utilizaremos para esto la fuente más confiable que existe, el código generado para la maquina virtual.

Hemos de investigar el fenomeno de concatenación con un ejemplo creíble, uno donde uno de los valores es una variable obtenida “de algun otro lado”, pues en caso de que todos los valores son definidos, por ejemplo en:

public static void main(String[] args) {
String string4 = "Cadena 1" + "Cadena 2" + "Cadena 3";
}

Java genera código optimizado donde todas las cadenas realmente son convertidas a una sola en tiempo de compilación, generando código para la JVM como sigue:

public static void main(java.lang.String[]);
Code:
0:   ldc     #16; //String Cadena 1Cadena 2Cadena 3
2:   astore_1
3:   return

Podemos ver aquí que se carga la cadena indicada como #16, y tal cadena es la concatenación de las 3 diferentes que realmente habíamos especificado.

Intentemos pues con variables de verdad, en los sigiuentes ejemplos, indicare con la variable “value” un valor supuestamente indefinido, aunque en el siguiente ejemplo esto no tendra mucho sentido, en los sucesivos sí lo tendrá.

Caso de concatenación 1.

Crear objetos String para todos las cadenas que deseamos concatenar y finalmente concatenarlas en una sola:

public static void main(String[] args) {
String value = "Valor";

String s1 = "Cadena 1";
String s2 = "Cadena 2";
String s3 = "Cadena 3";
String string1 = s1 + value + s2 + s3;
}

El código generado es más largo pero no por eso más complicado:

public static void main(java.lang.String[]);
Code:
0:   ldc     #16; //String Valor
2:   astore_1
3:   ldc     #18; //String Cadena 1
5:   astore_2
6:   ldc     #20; //String Cadena 2
8:   astore_3
9:   ldc     #22; //String Cadena 3
11:  astore  4
13:  new     #24; //class java/lang/StringBuilder
16:  dup
17:  aload_2
18:  invokestatic    #26; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
21:  invokespecial   #32; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
24:  aload_1
25:  invokevirtual   #35; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28:  aload_3
29:  invokevirtual   #35; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32:  aload   4
34:  invokevirtual   #35; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37:  invokevirtual   #39; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
40:  astore  5
42:  return

La primer sorpresa al ver el código es que el mito de concatenar con + no es menos efectivo que utilizar un StringBuilder, pues el compilador nuevamente realiza algo de optimización y utiliza de forma interna un objeto StringBuilder para realizar la concatenación. Ahorrando simplemente los store del Caso 1, que no son otra cosa que la inicialización de los String.

public static void main(String[] args) {
String value = "Valor";
String string2 = "Cadena1" + value + "Cadena2" + "Cadena3";
}
public static void main(java.lang.String[]);
Code:
0:   ldc     #16; //String Valor
2:   astore_1
3:   new     #18; //class java/lang/StringBuilder
6:   dup
7:   ldc     #20; //String Cadena1
9:   invokespecial   #22; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
12:  aload_1
13:  invokevirtual   #25; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16:  ldc     #29; //String Cadena2
18:  invokevirtual   #25; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21:  ldc     #31; //String Cadena3
23:  invokevirtual   #25; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26:  invokevirtual   #33; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29:  astore_2
30:  return

Caso 3:
Utilizando un objeto StringBuilder.
Curiosamente esta forma es mas lenta que utilizar el operador +, pues si bien, a primera vista pudiera parecer que no hace proceso extra, sí tiene que hacerlo, sí vemos el codigo de la JVM encontramos unos misterioros pop y aload_2, el problema aquí es que StringBuilder.append devuelve un valor, y una vez que el Thread principal vuelve de la subrutina los valores dejados en el stack tienen que ser removidos aun si no se estan utilizando. Es curioso que no se optimice esto de la forma en que se hizo en el caso dos, donde no existen los pop, pues de alguna forma el compilador sabe que no se utilizan y nunca existe un valor de retorno siquiera.

public static void main(String[] args) {
String value = "Valor";
StringBuilder sb = new StringBuilder();
sb.append("Cadena 1");
sb.append("Cadena 2");
sb.append("Cadena 3");
String string3 = sb.toString();
}
public static void main(java.lang.String[]);
Code:
0:   ldc     #16; //String Valor
2:   astore_1
3:   new     #18; //class java/lang/StringBuilder
6:   dup
7:   invokespecial   #20; //Method java/lang/StringBuilder."<init>":()V
10:  astore_2
11:  aload_2
12:  ldc     #21; //String Cadena 1
14:  invokevirtual   #23; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17:  pop
18:  aload_2
19:  ldc     #27; //String Cadena 2
21:  invokevirtual   #23; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24:  pop
25:  aload_2
26:  ldc     #29; //String Cadena 3
28:  invokevirtual   #23; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31:  pop
32:  aload_2
33:  invokevirtual   #31; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36:  astore_3
37:  return

No hace falta siquiera terminar de interpretar compilado para darse cuenta que la version mas corta es la producida por

String string2 = "Cadena1" + value + "Cadena2" + "Cadena3";

Esto resulta muy interesante, pues gracias a este tipo de optimizaciones no es necesario que java incluya un método para dividir una cadena en varias líneas, incluso, no es necesario pues si solamente estuvieramos utilizando constantes, todos nuestros valores serian concatenados en una sola cadena en tiempo de compilacion.

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*