Java básico: String (I)
Lunes, 24 de Octubre de 2005Cuando 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.
















Octubre 28th, 2005 2:14 pm
Mmm… ?estás seguro de que el compilador es suficientemente inteligente para eso? Tengo entendido que cuando concatenas varios Strings con el operador “+”, la máquina virtual crea una cadena nueva por cada “par”. Es decir en
String algo = "esto " + "es " + "una tontería";la VM crea primero un String “esto es ” y luego crea el String definitivo “esto es una tontería”.
Al menos eso pone en muchos libros. ?Tal vez, es alguna mejora de las últimas versiones?
Octubre 28th, 2005 8:46 pm
Lo que dices es cierto. Si concatenas constantes, el compilador creara una constante nueva con la suma de las anteriores. Es igual que el ejemplo que pongo de:
Es más, si por un casual intentamos concatenar constantes:
System.out.println(”Pulsa cualquier tecla\n”+
“para continuar\n”);
Java lo detecta y las concatena en tiempo de compilación, sin generar código redundante:
System.out.println(”Pulsa cualquier tecla\npara continuar\n”);
Pero si no son constantes, utilizará StringBuffer. Te invito a que lo compruebes con un decompilador. Veras como Java te “toquetea” el codigo. :-)
Noviembre 2nd, 2005 7:13 pm
He probado algunos ejemplos con el Jad (incluídos los que pones) y no utiliza StringBuffers. Sólo optimiza cuando concateno constantes.
Yo utilizo la versión 1.4. ?Tal vez es una mejora de la 1.5? Eso sí, el Jad me dice que la versión del fichero class es la 48.0, y que no la soporta.
Noviembre 8th, 2005 12:08 pm
Con que lo compilas? Algun IDE en concreto (IDEA, JBuilder, Eclipse) o directamente desde la linea de comandos con javac?
Marzo 26th, 2006 11:07 pm
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.
Marzo 27th, 2006 10:47 am
Agradezco tu comentario Sole, pero no he tenido mas remedio que modificar tu texto para quitar los casi 90 enlaces que has metido a una unica paginas personal (no se si tuya) por ser considerado como spam. Un saludo.
Noviembre 27th, 2007 1:14 pm
[…] http://albertovilches.com/java_basico_string_i (in spanish) […]