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