Cuando aprendemos Java, vamos aprendiendo aspectos del lenguaje muy peculiares. Uno de ellos es que “todo es un objeto”, incluso las cadenas de texto que son del tipo java.lang.String. Después aprendemos que Java no sabe concatenar cadenas y que el operador “+” es el único que está “sobrecargado”. Es decir, el operador “+” en Java equivale a crear un nuevo objeto String distinto cuyo contenido es la suma de los dos objetos String que acabamos de concatenar.
Y por esta razón a veces vemos código como el siguiente:
long result = System.currentTimeMillis();
System.out.println("El resultado final es "+result+ " milisegundos");
E, ingenuos de nosotros, nos entra una desagradable sensación de culpabilidad, por lo que nos apiadamos de Java y decidimos ponérselo más fácil, sustituyéndo el código tan costoso por esta versión supuestamente optimizada:
long result = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
sb.append("El resultado final es ");
sb.append(result);
sb.append(" milisegundos");
System.out.println(sb.toString());
Pues no ha servido para nada. Los dós códigos son equivalentes (que no iguales). Y es que cómo Java no sabe sumar cadenas, cuando se encuentra un código como el primero, el compilador lo sustituye raudo y veloz por un código como el siguiente:
long result = System.currentTimeMillis();
System.out.println(
new StringBuffer().append("El resultado final es ")
.append(result).append(" milisegundos").toString());
Por lo que nuestra optimización inicial, aunque bien intencionada, no ha servido para nada. Java ya sabe hacer eso por nosotros. Caer en la tentación del principiante de intentar optimizar el código de nuestra aplicación, sustituyendo cada concatenación que vemos por la utilización de StringBuffers es un esfuerzo innecesario que no debemos malgastar.
Es más, si por un casual intentamos concatenar constantes:
System.out.println("Pulsa cualquier teclan"+
"para continuarn");
Java lo detecta y las concatena en tiempo de compilación, sin generar código redundante:
System.out.println("Pulsa cualquier teclanpara continuarn");
Pero vayamos a otro caso distinto. Supongamos este codigo:
String numeros = "";
for (int n=1;n<100;n++) {
numeros = numeros + n + " ";
}
System.out.println(numeros);
?Qué está pasando aquí? Bueno, pues si sabemos que Java no sabe concatenar y que, por cada concatenación va a construir un objeto String nuevo, podemos llegar a la conclusión de que así, a ojo, va a crear 100 objetos String intermedios para solo quedarse con uno, el último de ellos.
Tomando como ejemplo el caso anterior, veamos lo que haría el compilador:
String numeros = "";
for (int n=1;n<100;n++) {
numeros = new StringBuffer().append(numeros).append(n).append(" ").toString();
}
System.out.println(numeros);
Está claro que no hemos ganado nada. Se siguen creando y destruyendo 100 objetos String y StringBuffer, y solo nos quedamos con la última instancia del String resultante. Nada óptimo, sin lugar a dudas (el consumo másivo de objetos en memoria sin justificación es el mayor enemigo del rendimiento de nuestra aplicación). Así que la solución es fácil: ya que sabemos usar StringBuffer, aprovechemos lo que sabemos para ahorrarle trabajo a Java. El código ideal sería así:
StringBuffer numeros = new StringBuffer();
for (int n=1;n<100;n++) {
numeros.append(n).append(" ");
}
System.out.println(numeros.toString());
Ahora si que le hemos ahorrado a Java trabajo, pero le hemos ahorrado el trabajo de la generación innecesaria de objetos, no la de concatenar.
Conclusión:
- Java si sabe concatenar: Cada vez que aparece una concatenación, crea un objeto StringBuffer y sustituye el operador “+” tantas veces cómo sea necesario por llamadas a métodos append() de la clase StringBuffer. Esta sustitución es atómica y solo se realiza una vez por cada línea, por lo que cada vez que queramos concatenar cadenas, intentaremos siempre hacerlo en la misma línea.
- Java no sabe ahorrar: Si utilizamos la concatenación dentro de un bucle, Java realizará la sustitución anterior en el interior del mismo, con el consiguiente desperdicio de memoria al crear tantas instancias de String y StringBuffer como repeticiones tenga el bucle. Solo en este caso, si procede utilizar StringBuffer.
Os invito a que probeis diferentes versiones de concatenación de Strings, las compileis y después las decompileis. Un buen descompilador que utilizo con frecuencia es Jad. Veréis como Java es más listo de lo que parece… en algunas ocasiones.