Por qué String es inmutable
En Java, los objetos String son inmutables: una vez creados, su contenido no puede modificarse. Cuando escribes s = s + "x", no estás mutando s; estás creando un nuevo String y reasignando la referencia. Esta decisión de diseño tiene razones importantes:
- Thread-safety: varios hilos pueden compartir la misma
Stringsin sincronización. - Caching del hashCode: se calcula una vez y se reutiliza.
- String pool: literales iguales se reutilizan en memoria (
"hola" == "hola"puede sertrue). - Seguridad: cadenas pasadas a APIs sensibles (rutas de archivo, SQL) no pueden modificarse por terceros.
El contrapunto: cualquier operación que "cambie" un String crea un objeto nuevo. Concatenar muchas veces desperdicia memoria y tiempo.
El problema con + en bucles
Supón que quieres construir una cadena con 10 000 líneas:
String resultado = "";
for (int i = 0; i < 10_000; i++) {
resultado += "línea " + i + "\n"; // crea un String nuevo cada iteración
}
Cada += genera internamente:
- Un
StringBuildertemporal - Un nuevo
Stringfinal - El
Stringanterior queda para recolección de basura
Con 10 000 iteraciones son 10 000 objetos temporales. El rendimiento es terrible en cadenas grandes.
StringBuilder: cadenas mutables
StringBuilder es la clase estándar para construir cadenas paso a paso. Internamente usa un array de caracteres que se redimensiona según crece el contenido.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) {
sb.append("línea ").append(i).append('\n');
}
String resultado = sb.toString();
Ventajas:
- Una sola asignación final de
String(entoString()). - Redimensionado eficiente: duplica la capacidad al llenarse.
- API fluida: cada método devuelve el propio
StringBuilder, permitiendo encadenamiento.
Métodos principales
| Método | Función |
|--------|---------|
| append(Object) | Añade al final. Acepta cualquier tipo primitivo y objeto. |
| insert(int offset, Object) | Inserta en una posición concreta. |
| delete(int start, int end) | Elimina un rango. |
| deleteCharAt(int index) | Elimina un solo carácter. |
| replace(int start, int end, String str) | Reemplaza un rango por otro string. |
| reverse() | Invierte la secuencia. |
| charAt(int index) | Devuelve el carácter en la posición. |
| setCharAt(int index, char ch) | Sustituye un carácter puntual. |
| length() | Longitud actual. |
| capacity() | Capacidad del buffer interno. |
| ensureCapacity(int min) | Garantiza al menos min de capacidad. |
| toString() | Devuelve el contenido como String inmutable. |
Ejemplo mostrando varios:
StringBuilder sb = new StringBuilder("Hola mundo");
sb.insert(4, " bello"); // "Hola bello mundo"
sb.append("!"); // "Hola bello mundo!"
sb.replace(5, 10, "grande"); // "Hola grande mundo!"
sb.deleteCharAt(sb.length()-1); // "Hola grande mundo"
sb.reverse(); // "odnum ednarg aloH"
String s = sb.toString();
Capacidad inicial
Si sabes aproximadamente cuánto crecerá la cadena, pasa la capacidad inicial al constructor para evitar redimensionados:
StringBuilder sb = new StringBuilder(1024); // reserva 1024 chars
El valor por defecto es 16. Cada vez que se llena, la capacidad se duplica (nuevaCap = (viejaCap + 1) * 2). Reservar de más es mejor que redimensionar muchas veces.
StringBuilder vs StringBuffer
StringBuffer es la versión thread-safe de StringBuilder: sus métodos están sincronizados. En código monohilo (la mayoría) StringBuilder es más rápido. Solo usa StringBuffer si varios hilos modifican la misma instancia simultáneamente, algo poco frecuente en diseños modernos.
| Aspecto | StringBuilder | StringBuffer | |---------|---------------|--------------| | Thread-safe | No | Sí (synchronized) | | Velocidad | Más rápida | Más lenta | | Disponible desde | Java 5 | Java 1.0 | | Uso recomendado | Monohilo | Multihilo compartido |
Casos de uso habituales
Construir SQL dinámico
StringBuilder sql = new StringBuilder("SELECT * FROM productos WHERE 1=1");
if (filtroNombre != null) sql.append(" AND nombre LIKE ?");
if (precioMin > 0) sql.append(" AND precio >= ?");
if (precioMax > 0) sql.append(" AND precio <= ?");
sql.append(" ORDER BY nombre");
Generar CSV
public String toCsv(List<Producto> productos) {
StringBuilder sb = new StringBuilder(productos.size() * 64);
sb.append("id,nombre,precio\n");
for (Producto p : productos) {
sb.append(p.id())
.append(',').append(p.nombre())
.append(',').append(p.precio())
.append('\n');
}
return sb.toString();
}
Plantilla con reemplazos
StringBuilder html = new StringBuilder("""
<div>
<h1>__TITULO__</h1>
<p>__CUERPO__</p>
</div>
""");
int i = html.indexOf("__TITULO__");
html.replace(i, i + "__TITULO__".length(), "Bienvenido");
i = html.indexOf("__CUERPO__");
html.replace(i, i + "__CUERPO__".length(), "Contenido generado");
Cuándo NO usar StringBuilder
El compilador moderno optimiza automáticamente concatenaciones simples:
String mensaje = "Hola " + nombre + ", tienes " + edad + " años.";
Esto se transforma en una sola operación eficiente (en Java 9+ usa invokedynamic con StringConcatFactory, incluso mejor que StringBuilder). No necesitas StringBuilder para concatenaciones puntuales fuera de bucles.
Regla práctica: usa StringBuilder cuando la concatenación ocurre dentro de un bucle, una recursión o condicional complejo. Para expresiones simples, deja que el compilador optimice.
Alternativas modernas
String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements): para unir con separador.Collectors.joining(): en streams.String.format()/formatted(): para plantillas con placeholders.- Text blocks +
formatted(): para cadenas largas con sustituciones.
Ejemplo con String.join:
List<String> partes = List.of("uno", "dos", "tres");
String unido = String.join(", ", partes); // "uno, dos, tres"
StringBuilder sigue siendo la herramienta correcta cuando necesitas construir incrementalmente, modificar in-place o insertar en posiciones arbitrarias.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Java
Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender por qué String es inmutable en Java. Diferenciar String, StringBuilder y StringBuffer. Usar append, insert, delete, reverse, replace sobre StringBuilder. Medir el impacto de usar + dentro de bucles vs StringBuilder. Aplicar StringBuilder en casos reales: builders de SQL, plantillas, CSV. Elegir la capacidad inicial adecuada.