En Java hay tres clases para trabajar con texto: String, StringBuilder y StringBuffer. La diferencia cabe en una línea: String no se puede modificar, las otras dos sí, y de esas dos solo StringBuffer está sincronizada para usarse desde varios hilos. Lo interesante es lo que eso significa en la práctica, y aquí lo vemos con ejemplos probados en Java 25.
| String | StringBuilder | StringBuffer | |
|---|---|---|---|
| ¿Se puede modificar? | No (inmutable) | Sí | Sí |
| ¿Segura entre hilos? | Sí, porque no cambia | No | Sí, sus métodos están sincronizados |
| Velocidad al construir texto | Lenta si se concatena en un bucle | La más rápida | Algo más lenta que StringBuilder |
| Desde | Java 1.0 | Java 5 | Java 1.0 |
| Úsala para… | Casi todo: valores, claves, parámetros | Construir texto por partes | Código antiguo o un búfer compartido entre hilos |
String es inmutable
Un objeto String no cambia nunca después de crearse. Cuando parece que lo modificas, por ejemplo con +, replace o toUpperCase, en realidad Java crea un String nuevo y deja el original como estaba. Con StringBuilder pasa lo contrario: append modifica el mismo objeto, y cualquier variable que apunte a él ve el cambio.
public class Inmutable {
public static void main(String[] args) {
String saludo = "Hola";
String otro = saludo;
saludo = saludo + " mundo"; // crea un String nuevo
System.out.println(saludo); // Hola mundo
System.out.println(otro); // Hola: el original no cambió
StringBuilder sb = new StringBuilder("Hola");
StringBuilder mismo = sb;
sb.append(" mundo"); // modifica el mismo objeto
System.out.println(sb); // Hola mundo
System.out.println(mismo); // Hola mundo: es el mismo objeto
}
}java InmutableHola mundo Hola Hola mundo Hola mundo
La inmutabilidad no es un capricho. Permite que Java comparta las cadenas literales en memoria, que un String sirva sin riesgo como clave de un HashMap y que se pueda pasar a cualquier método o hilo sabiendo que nadie lo va a alterar. Lo explicamos con más detalle en qué es un objeto inmutable en Java.
El problema de concatenar en un bucle
La inmutabilidad tiene un coste cuando se construye un texto poco a poco. Cada s += algo crea un String nuevo y copia en él todo lo que había antes. En un bucle de cien mil vueltas, eso son cien mil copias de un texto cada vez más largo. StringBuilder, en cambio, va añadiendo al final de un búfer interno que solo crece de vez en cuando.
public class Bucle {
static final int N = 100_000;
static String conMas() {
String s = "";
for (int i = 0; i < N; i++) {
s += i % 10; // copia todo el texto en cada vuelta
}
return s;
}
static String conBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
sb.append(i % 10); // añade al final del mismo búfer
}
return sb.toString();
}
public static void main(String[] args) {
for (int ronda = 0; ronda < 3; ronda++) { // la primera ronda calienta la JVM
long t0 = System.nanoTime();
String a = conMas();
long t1 = System.nanoTime();
String b = conBuilder();
long t2 = System.nanoTime();
System.out.printf("ronda %d: con + %d ms | con StringBuilder %,.2f ms | iguales: %b%n",
ronda + 1, (t1 - t0) / 1_000_000, (t2 - t1) / 1e6, a.equals(b));
}
}
}java Bucleronda 1: con + 751 ms | con StringBuilder 3.91 ms | iguales: true ronda 2: con + 559 ms | con StringBuilder 0.59 ms | iguales: true ronda 3: con + 499 ms | con StringBuilder 0.46 ms | iguales: true
El resultado es el mismo texto, pero con + tarda unas doscientas veces más en la primera ronda y más de mil veces más en las siguientes. El tiempo exacto depende del equipo y cambia en cada ejecución, pero la diferencia de escala no: con + el trabajo crece con el cuadrado del número de vueltas, y con StringBuilder, de forma lineal. La primera ronda es más lenta en los dos casos porque la máquina virtual todavía no ha optimizado el código.
+ en una sola expresión, como "Hola, " + nombre + ".", está bien y es lo más legible. El texto original de esta entrada, de 2012, decía que Java lo convierte internamente en un StringBuffer o un StringBuilder. Eso era cierto hasta Java 8. Desde Java 9, el compilador genera una llamada a makeConcatWithConstants y la máquina virtual elige la mejor forma de unir las piezas al ejecutar el programa. Se puede ver con javap.public class Concat {
static String saludo(String nombre, int edad) {
return "Hola, " + nombre + ". Tienes " + edad + " años.";
}
public static void main(String[] args) {
System.out.println(saludo("Ana", 30));
}
}javap -c Concat (extracto) static java.lang.String saludo(java.lang.String, int);
Code:
0: aload_0
1: iload_1
2: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
7: areturnLos métodos de StringBuilder
StringBuilder y StringBuffer tienen exactamente los mismos métodos. Los más usados son append para añadir al final, insert para meter texto en una posición, delete y replace para quitar o sustituir un tramo, setCharAt para cambiar un carácter, reverse para darle la vuelta y toString para obtener el String final.
public class Metodos {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("decodigo");
sb.append(".com"); // decodigo.com
sb.insert(0, "https://"); // https://decodigo.com
System.out.println(sb);
sb.replace(0, 8, ""); // quita "https://"
sb.setCharAt(0, 'D'); // Decodigo.com
System.out.println(sb);
sb.delete(sb.length() - 4, sb.length()); // quita ".com"
System.out.println(sb.reverse()); // ogidoceD
System.out.println("longitud " + sb.length() + ", capacidad " + sb.capacity());
}
}java Metodoshttps://decodigo.com Decodigo.com ogidoceD longitud 8, capacidad 24
La capacidad es el tamaño del búfer interno: al crear un StringBuilder a partir de un texto, Java reserva su longitud más 16 caracteres, por eso sale 24. Cuando el texto no cabe, el búfer se amplía solo. Si sabes de antemano que vas a construir un texto grande, puedes indicar la capacidad inicial con new StringBuilder(10_000) y te ahorras esas ampliaciones.
StringBuffer y los hilos
La única diferencia entre StringBuffer y StringBuilder es que los métodos de StringBuffer están sincronizados: si dos hilos llaman a append a la vez, uno espera a que termine el otro. En este ejemplo, dos hilos añaden cien mil caracteres cada uno al mismo objeto.
public class Hilos {
public static void main(String[] args) throws InterruptedException {
for (int prueba = 1; prueba <= 3; prueba++) {
StringBuilder builder = new StringBuilder();
StringBuffer buffer = new StringBuffer();
Runnable tarea = () -> {
for (int i = 0; i < 100_000; i++) {
builder.append('x');
buffer.append('x');
}
};
Thread h1 = new Thread(tarea), h2 = new Thread(tarea);
h1.start(); h2.start();
h1.join(); h2.join();
System.out.printf("prueba %d: StringBuilder %d | StringBuffer %d (esperado 200000)%n",
prueba, builder.length(), buffer.length());
}
}
}java Hilosprueba 1: StringBuilder 193695 | StringBuffer 200000 (esperado 200000) prueba 2: StringBuilder 196891 | StringBuffer 200000 (esperado 200000) prueba 3: StringBuilder 176761 | StringBuffer 200000 (esperado 200000)
Con StringBuilder se pierden caracteres, y en cada ejecución una cantidad distinta: los dos hilos escriben a la vez en la misma posición del búfer. A veces el programa incluso falla con una ArrayIndexOutOfBoundsException. Con StringBuffer siempre salen los 200000.
StringBuilder es lo correcto y no necesita sincronización. Compartir un búfer de texto entre hilos es raro, y la sincronización de StringBuffer solo protege cada llamada por separado: dos append seguidos de un hilo pueden quedar intercalados con los de otro. StringBuffer existe sobre todo por compatibilidad con el código anterior a Java 5, cuando no había otra opción.Cuidado al comparar
Un error clásico: StringBuilder no redefine equals, así que dos objetos con el mismo texto no son «iguales» para Java. Para comparar el contenido, hay que pasarlos a String, usar compareTo, disponible desde Java 11, o contentEquals.
public class Comparar {
public static void main(String[] args) {
StringBuilder a = new StringBuilder("java");
StringBuilder b = new StringBuilder("java");
System.out.println(a.equals(b)); // false: compara si son el mismo objeto
System.out.println(a.toString().equals(b.toString())); // true
System.out.println(a.compareTo(b) == 0); // true (desde Java 11)
System.out.println("java".contentEquals(a)); // true
}
}java Compararfalse true true true
Alternativas modernas
Muchas veces ni siquiera hace falta un StringBuilder. Para unir una lista con un separador están String.join y Collectors.joining; para repetir un texto, repeat; para dar formato, formatted; y para textos de varias líneas, los bloques de texto con tres comillas. Por debajo usan StringBuilder o algo equivalente, y el código queda más claro.
import java.util.List;
import java.util.stream.Collectors;
public class Modernos {
public static void main(String[] args) {
List<String> lenguajes = List.of("Java", "Go", "Rust");
System.out.println(String.join(", ", lenguajes)); // Java, Go, Rust
System.out.println(lenguajes.stream().collect(Collectors.joining(" | ", "[", "]")));
System.out.println("-".repeat(20)); // Java 11
System.out.println("%s tiene %d letras".formatted("Java", 4)); // Java 15
String json = """
{"lenguaje": "Java", "version": 25}
"""; // bloque de texto, Java 15
System.out.print(json);
}
}java ModernosJava, Go, Rust
[Java | Go | Rust]
--------------------
Java tiene 4 letras
{"lenguaje": "Java", "version": 25}Cuál usar
- String para casi todo: guardar un texto, pasarlo entre métodos o usarlo como clave. Y
+para concatenar en una sola expresión. - StringBuilder cuando construyes un texto en un bucle o por partes. Es la opción por defecto para texto que cambia.
- StringBuffer solo si mantienes código antiguo que ya lo usa o si de verdad varios hilos escriben en el mismo búfer, y aun así conviene pensar si no es mejor que cada hilo tenga el suyo.
- String.join, Collectors.joining, repeat o los bloques de texto cuando encajan, porque hacen lo mismo con menos código.
Si estás empezando con Java, en el curso de Java tienes los tipos y métodos explicados desde cero, y en medir el rendimiento en Java, cómo comparar tiempos como los de este artículo con más rigor.
Todos los ejemplos se compilaron y ejecutaron con Java 25 (Eclipse Temurin 25.0.4). Los tiempos y los recuentos de la prueba con hilos cambian en cada ejecución. Referencias: documentación de StringBuilder y StringBuffer, y JEP 280: concatenación con invokedynamic.
Podrían poner un ejemplo porfa?
Espero tener algunos pronto 🙂
lo siento no se que se mutable , podrian explicar
En otras palabras un objeto mutable es aquel que puede cambiar o puede alterar su valor
en visual c++ cual seria el equivalente a stringbufer?
en visual c++ cual seria el equivalente a stringbufer?
pinshis burros