Manejo de cadenas: String y StringBuilder
Introducción
El manejo de cadenas de texto es una de las operaciones más comunes en cualquier lenguaje de programación, y Java ofrece un conjunto robusto de herramientas para trabajar con ellas. Las cadenas son tan importantes que Java proporciona una clase especial llamada String
que está integrada directamente en el lenguaje, además de clases auxiliares como StringBuilder
y StringBuffer
para operaciones más específicas.
En este artículo exploraremos cómo trabajar con textos en Java, desde operaciones básicas hasta técnicas más avanzadas. Comprenderemos la inmutabilidad de los objetos String
y cómo utilizar alternativas mutables cuando necesitemos realizar numerosas modificaciones en una cadena, mejorando así el rendimiento de nuestras aplicaciones.
La clase String
En Java, una cadena de texto se representa mediante un objeto de la clase String
. A diferencia de otros lenguajes donde las cadenas son simples arrays de caracteres, en Java son objetos completos con numerosos métodos para manipularlas.
Creación de cadenas
Hay varias formas de crear objetos String
:
// Mediante literal de cadena
String saludo = "Hola, mundo!";
// Mediante el constructor
String nombre = new String("Juan");
// A partir de un array de caracteres
char[] letras = {'J', 'a', 'v', 'a'};
String lenguaje = new String(letras);
// Concatenando otras cadenas
String mensaje = "Bienvenido " + "a " + "Java!";
Inmutabilidad de String
Una característica fundamental de los objetos String
en Java es que son inmutables, lo que significa que una vez creados, su contenido no puede cambiar. Cada operación que parece modificar una cadena en realidad crea un nuevo objeto String
:
String original = "Hola";
original = original + " mundo"; // Crea un nuevo objeto String
System.out.println(original); // Imprime: "Hola mundo"
Esta inmutabilidad tiene importantes ventajas:
- Mayor seguridad (las cadenas no pueden ser modificadas por código malintencionado)
- Posibilidad de compartir cadenas en memoria (pool de strings)
- Capacidad para usar cadenas como claves en colecciones como
HashMap
Métodos principales de String
La clase String
proporciona numerosos métodos útiles:
String texto = "Aprendiendo Java";
// Longitud de la cadena
int longitud = texto.length(); // 16
// Acceso a caracteres individuales
char primeraLetra = texto.charAt(0); // 'A'
// Subcadenas
String subcadena = texto.substring(12); // "Java"
String extracto = texto.substring(0, 11); // "Aprendiendo"
// Búsqueda
int posicion = texto.indexOf("Java"); // 12
boolean contiene = texto.contains("end"); // true
// Reemplazo
String reemplazado = texto.replace("Java", "Python"); // "Aprendiendo Python"
// Conversión de casos
String mayusculas = texto.toUpperCase(); // "APRENDIENDO JAVA"
String minusculas = texto.toLowerCase(); // "aprendiendo java"
// Eliminación de espacios en blanco
String conEspacios = " texto con espacios ";
String sinEspacios = conEspacios.trim(); // "texto con espacios"
// División de cadenas
String nombres = "Ana,Luis,Carmen,Pedro";
String[] arrayNombres = nombres.split(","); // ["Ana", "Luis", "Carmen", "Pedro"]
// Comprobación de inicio y fin
boolean empiezaCon = texto.startsWith("Apren"); // true
boolean terminaCon = texto.endsWith("va"); // true
// Comparación de cadenas
String cadena1 = "abc";
String cadena2 = "abc";
boolean sonIguales = cadena1.equals(cadena2); // true
boolean ignorandoMayusculas = "ABC".equalsIgnoreCase("abc"); // true
// Comparación lexicográfica (para ordenación)
int comparacion = "abc".compareTo("abd"); // Devuelve un valor negativo (-1)
El pool de strings
Java mantiene un pool especial de cadenas para optimizar el uso de memoria:
String s1 = "Hola"; // Se almacena en el pool de strings
String s2 = "Hola"; // Reutiliza la misma referencia desde el pool
String s3 = new String("Hola"); // Crea un nuevo objeto fuera del pool
System.out.println(s1 == s2); // true, misma referencia
System.out.println(s1 == s3); // false, diferentes referencias
System.out.println(s1.equals(s3)); // true, mismo contenido
Para forzar la inserción de un objeto String
en el pool:
String s4 = s3.intern(); // Inserta s3 en el pool o devuelve la referencia si ya existe
System.out.println(s1 == s4); // true, ahora es la misma referencia
La clase StringBuilder
Cuando necesitamos realizar muchas modificaciones a una cadena, usar String
puede ser ineficiente debido a su inmutabilidad. Para estos casos, Java proporciona la clase StringBuilder
, que permite modificar una cadena sin crear nuevos objetos.
Creación y uso básico
// Creación de un StringBuilder
StringBuilder builder = new StringBuilder();
// Añadir contenido
builder.append("Hola ");
builder.append("mundo!");
// Convertir a String cuando hayamos terminado
String resultado = builder.toString(); // "Hola mundo!"
Métodos principales de StringBuilder
StringBuilder sb = new StringBuilder("Java");
// Añadir al final
sb.append(" es ").append("genial!"); // "Java es genial!"
// Insertar en posición específica
sb.insert(0, "Lenguaje "); // "Lenguaje Java es genial!"
// Eliminar caracteres
sb.delete(0, 9); // "Java es genial!"
sb.deleteCharAt(8); // "Java es enial!"
// Reemplazar sección
sb.replace(8, 14, "increíble"); // "Java es increíble!"
// Invertir la cadena
sb.reverse(); // "!elbíercni se avaJ"
// Volvemos a invertir para continuar
sb.reverse(); // "Java es increíble!"
// Capacidad y longitud
int capacidad = sb.capacity(); // Capacidad interna del buffer
int longitud = sb.length(); // Longitud actual de la cadena
// Establecer longitud (trunca o amplía con null)
sb.setLength(8); // "Java es "
StringBuffer vs StringBuilder
Java también ofrece la clase StringBuffer
, similar a StringBuilder
pero con métodos sincronizados que la hacen segura para entornos multihilo:
StringBuffer buffer = new StringBuffer("Texto seguro en múltiples hilos");
La diferencia principal es:
StringBuilder
: Más rápido pero no es seguro en entornos multihiloStringBuffer
: Más lento pero thread-safe (seguro en concurrencia)
En aplicaciones modernas, se prefiere usar StringBuilder
y gestionar la sincronización a nivel más alto cuando sea necesario.
Cuándo usar String, StringBuilder o StringBuffer
-
Usa
String
:- Para cadenas que no cambiarán
- Para almacenar en colecciones
- Para paso de parámetros y valores de retorno
-
Usa
StringBuilder
:- Cuando necesites modificar frecuentemente una cadena
- En bucles que construyen cadenas
- En entornos de un solo hilo
-
Usa
StringBuffer
:- Cuando necesites modificar cadenas en entornos multihilo
- Cuando la seguridad en concurrencia sea crítica
Rendimiento y buenas prácticas
Concatenación eficiente
// Ineficiente (crea múltiples objetos String temporales)
String ineficiente = "";
for (int i = 0; i < 10000; i++) {
ineficiente += "a";
}
// Eficiente (modifica el mismo objeto)
StringBuilder eficiente = new StringBuilder();
for (int i = 0; i < 10000; i++) {
eficiente.append("a");
}
String resultado = eficiente.toString();
Dimensionamiento inicial
Si conoces aproximadamente el tamaño final de tu cadena, puedes mejorar el rendimiento especificando una capacidad inicial:
// Dimensionamiento inicial para evitar realocaciones
StringBuilder sb = new StringBuilder(1000);
Formateo de cadenas
Java ofrece un sistema de formateo similar al de C:
String formateada = String.format("Hola, %s. Tienes %d años.", "María", 28);
// "Hola, María. Tienes 28 años."
// Lo mismo pero con salida directa a consola
System.out.printf("Pi es aproximadamente %.2f\n", Math.PI);
// "Pi es aproximadamente 3.14"
Text Blocks (desde Java 15)
En versiones modernas de Java, puedes usar bloques de texto para cadenas multilínea:
String html = """
<html>
<body>
<h1>Título</h1>
<p>Párrafo con "comillas".</p>
</body>
</html>
""";
Ejercicios prácticos
Aquí tienes algunos ejercicios para practicar:
- Crear un programa que cuente el número de vocales en una cadena:
public class ContadorVocales {
public static void main(String[] args) {
String texto = "Programación en Java";
int contador = 0;
texto = texto.toLowerCase();
for (int i = 0; i < texto.length(); i++) {
char c = texto.charAt(i);
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
contador++;
}
}
System.out.println("Número de vocales: " + contador);
}
}
- Construir un palíndromo a partir de una palabra:
public class Palindromo {
public static void main(String[] args) {
String palabra = "Java";
StringBuilder sb = new StringBuilder(palabra);
String reverso = sb.reverse().toString();
String palindromo = palabra + reverso;
System.out.println(palindromo); // JavaavaJ
}
}
Resumen
El manejo eficiente de cadenas de texto es fundamental en el desarrollo con Java. La clase String
proporciona una forma segura e inmutable de trabajar con texto, mientras que StringBuilder
y StringBuffer
ofrecen alternativas mutables para situaciones donde se requieren múltiples modificaciones. Conocer las diferencias entre estas clases y cuándo utilizar cada una te permitirá escribir código más eficiente y elegante.
Recuerda que, aunque las cadenas parezcan simples, su correcto manejo puede tener un impacto significativo en el rendimiento de tus aplicaciones, especialmente en aquellas que procesan grandes volúmenes de texto o realizan numerosas transformaciones de cadenas.