Ir al contenido principal

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 multihilo
  • StringBuffer: 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:

  1. 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);
    }
}
  1. 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.