Métodos de manipulación básica (length, charAt, substring)
La clase String
en Java proporciona un conjunto de métodos fundamentales para manipular cadenas de texto. Estos métodos básicos permiten examinar y extraer información de las cadenas sin modificar la cadena original, ya que los objetos String son inmutables en Java.
Método length()
El método length()
se utiliza para obtener el número de caracteres que componen una cadena. Este método no recibe parámetros y devuelve un valor entero que representa la longitud de la cadena.
String texto = "Programación en Java";
int longitud = texto.length(); // Devuelve 20
System.out.println("La cadena tiene " + longitud + " caracteres");
length()
cuenta todos los caracteres, incluyendo espacios en blanco y caracteres especiales. Este método resulta esencial cuando se necesita:
- Validar la longitud de entradas de usuario
- Iterar sobre cada carácter de una cadena
- Verificar si una cadena está vacía (cuando length() devuelve 0)
String password = "abc123";
if (password.length() < 8) {
System.out.println("La contraseña debe tener al menos 8 caracteres");
}
Método charAt()
El método charAt(int index)
permite acceder a un carácter específico dentro de una cadena mediante su posición o índice. Los índices en Java comienzan en 0, por lo que el primer carácter está en la posición 0, el segundo en la posición 1, y así sucesivamente.
String palabra = "Java";
char primeraLetra = palabra.charAt(0); // Devuelve 'J'
char ultimaLetra = palabra.charAt(palabra.length() - 1); // Devuelve 'a'
Si se intenta acceder a un índice fuera del rango válido (negativo o mayor o igual a la longitud de la cadena), se lanza una excepción StringIndexOutOfBoundsException
. Por ello, se recomienda verificar siempre que el índice sea válido antes de llamar a este método.
String texto = "Hola";
int indice = 10;
if (indice >= 0 && indice < texto.length()) {
char caracter = texto.charAt(indice);
System.out.println("El carácter en la posición " + indice + " es: " + caracter);
} else {
System.out.println("Índice fuera de rango");
}
El método charAt()
se utiliza a menudo para:
- Recorrer una cadena carácter por carácter
- Verificar si una cadena contiene ciertos caracteres específicos
- Construir algoritmos de procesamiento de texto
String frase = "Java es un lenguaje de programación";
int contadorVocales = 0;
for (int i = 0; i < frase.length(); i++) {
char c = Character.toLowerCase(frase.charAt(i));
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
contadorVocales++;
}
}
System.out.println("La frase contiene " + contadorVocales + " vocales");
Método substring()
El método substring()
permite extraer una subcadena de una cadena existente. Existen dos variantes de este método:
- substring(int beginIndex): Extrae desde la posición indicada hasta el final de la cadena.
- substring(int beginIndex, int endIndex): Extrae desde la posición inicial hasta la posición final (sin incluir el carácter en la posición final).
String mensaje = "Aprendiendo Java";
// Extrae desde la posición 12 hasta el final
String lenguaje = mensaje.substring(12); // Devuelve "Java"
// Extrae desde la posición 0 hasta la 11 (sin incluir el carácter en la posición 11)
String accion = mensaje.substring(0, 11); // Devuelve "Aprendiendo"
El índice final no se incluye en el resultado. Si se quiere incluir el carácter en la posición n
, se debe usar n+1
como índice final.
Al igual que con charAt()
, si se proporcionan índices fuera del rango válido, se lanza una StringIndexOutOfBoundsException
. Además, si el índice inicial es mayor que el índice final, también se produce una excepción.
String url = "https://www.ejemplo.com/recursos/archivo.pdf";
// Extraer el protocolo
int indiceSeparador = url.indexOf("://");
if (indiceSeparador != -1) {
String protocolo = url.substring(0, indiceSeparador);
System.out.println("Protocolo: " + protocolo); // Muestra "https"
}
// Extraer la extensión del archivo
int indicePunto = url.lastIndexOf(".");
if (indicePunto != -1) {
String extension = url.substring(indicePunto + 1);
System.out.println("Extensión: " + extension); // Muestra "pdf"
}
El método substring()
se utiliza comúnmente para:
- Extraer partes significativas de una cadena (como dominios de URLs)
- Procesar formatos específicos (como fechas o códigos)
- Implementar algoritmos de análisis de texto
// Extraer componentes de una fecha en formato "dd/mm/aaaa"
String fecha = "23/04/2023";
String dia = fecha.substring(0, 2);
String mes = fecha.substring(3, 5);
String anio = fecha.substring(6);
System.out.println("Día: " + dia);
System.out.println("Mes: " + mes);
System.out.println("Año: " + anio);
Combinación de métodos básicos
Estos métodos de manipulación básica se pueden combinar para realizar operaciones más complejas sobre cadenas de texto:
String correo = "usuario@dominio.com";
// Verificar si es un correo válido (simplificado)
boolean esValido = correo.length() > 5 &&
correo.indexOf('@') > 0 &&
correo.indexOf('@') < correo.length() - 3;
// Extraer nombre de usuario y dominio
if (esValido) {
int posicionArroba = correo.indexOf('@');
String usuario = correo.substring(0, posicionArroba);
String dominio = correo.substring(posicionArroba + 1);
System.out.println("Usuario: " + usuario);
System.out.println("Dominio: " + dominio);
// Verificar primera letra del usuario
char primeraLetra = usuario.charAt(0);
if (Character.isLetter(primeraLetra)) {
System.out.println("El nombre de usuario comienza con una letra");
}
}
En este ejemplo se combinan los métodos length()
, indexOf()
, substring()
y charAt()
para analizar y validar una dirección de correo electrónico de forma básica.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Métodos de búsqueda y comparación
La clase String
en Java ofrece un conjunto de métodos para buscar información dentro de cadenas y realizar comparaciones entre ellas. Estos métodos son fundamentales para el procesamiento de texto y la validación de datos en aplicaciones.
Métodos de búsqueda
Los métodos de búsqueda permiten localizar subcadenas o caracteres específicos dentro de un String
. Estos métodos devuelven la posición donde se encuentra el elemento buscado o un valor negativo si no se encuentra.
indexOf() y lastIndexOf()
El método indexOf()
busca la primera ocurrencia de un carácter o subcadena, mientras que lastIndexOf()
busca la última ocurrencia:
String texto = "Java es un lenguaje versátil. Java es popular.";
// Búsqueda básica
int primera = texto.indexOf("Java"); // Devuelve 0
int ultima = texto.lastIndexOf("Java"); // Devuelve 25
// Búsqueda a partir de una posición específica
int segunda = texto.indexOf("Java", 1); // Devuelve 25
int anterior = texto.lastIndexOf("es", 20); // Devuelve 6
Estos métodos son extremadamente útiles para analizar y extraer partes de textos estructurados:
String html = "<div><p>Contenido del párrafo</p></div>";
int inicioParrafo = html.indexOf("<p>") + 3;
int finParrafo = html.indexOf("</p>");
if (inicioParrafo != -1 && finParrafo != -1) {
String contenido = html.substring(inicioParrafo, finParrafo);
System.out.println("Texto extraído: " + contenido);
}
contains()
El método contains()
verifica si una cadena contiene una subcadena específica, devolviendo un valor booleano:
String mensaje = "Aprende a programar en Java";
boolean contienePalabra = mensaje.contains("Java"); // true
boolean contienePython = mensaje.contains("Python"); // false
Se utiliza a menudo para validaciones simples:
String correo = "usuario@dominio.com";
if (!correo.contains("@")) {
System.out.println("Correo electrónico inválido");
} else {
System.out.println("Formato de correo válido");
}
startsWith() y endsWith()
Estos métodos verifican si una cadena comienza o termina con una subcadena específica:
String archivo = "documento.pdf";
if (archivo.startsWith("doc")) {
System.out.println("El archivo comienza con 'doc'");
}
if (archivo.endsWith(".pdf")) {
System.out.println("Es un archivo PDF");
}
También se pueden especificar posiciones de inicio para startsWith()
:
String url = "https://www.ejemplo.com";
boolean esSegura = url.startsWith("https"); // true
String ruta = "/usuarios/perfil/123";
boolean esPerfil = ruta.startsWith("/perfil", 9); // true
Métodos de comparación
Los métodos de comparación permiten determinar la relación entre cadenas, lo que es esencial para ordenamiento, búsqueda y validación.
equals() y equalsIgnoreCase()
El método equals()
compara si dos cadenas tienen exactamente el mismo contenido, mientras que equalsIgnoreCase()
realiza la misma comparación ignorando mayúsculas y minúsculas:
String usuario1 = "admin";
String usuario2 = "Admin";
boolean sonIguales = usuario1.equals(usuario2); // false
boolean sonIgualesIgnorandoMayus = usuario1.equalsIgnoreCase(usuario2); // true
Nunca se debe usar el operador ==
para comparar el contenido de Strings, ya que este compara referencias de objetos, no su contenido:
String a = "hola";
String b = new String("hola");
System.out.println(a == b); // false (referencias distintas)
System.out.println(a.equals(b)); // true (mismo contenido)
compareTo() y compareToIgnoreCase()
Estos métodos comparan cadenas lexicográficamente (orden alfabético) y devuelven un entero que indica su relación:
String palabra1 = "banana";
String palabra2 = "manzana";
int resultado = palabra1.compareTo(palabra2); // Negativo: palabra1 va antes
El valor devuelto indica:
- Negativo: la primera cadena va antes alfabéticamente
- Cero: las cadenas son iguales
- Positivo: la primera cadena va después alfabéticamente
Estos métodos son fundamentales para ordenar colecciones de cadenas:
List<String> frutas = new ArrayList<>(List.of("Manzana", "banana", "Pera", "kiwi"));
// Ordenar ignorando mayúsculas y minúsculas
frutas.sort((a, b) -> a.compareToIgnoreCase(b));
System.out.println(frutas); // [banana, kiwi, Manzana, Pera]
regionMatches()
Este método permite comparar regiones específicas de dos cadenas, ofreciendo gran flexibilidad:
String texto1 = "Programación en Java";
String texto2 = "Curso de JAVA avanzado";
// Comparar "Java" con "JAVA" ignorando mayúsculas
boolean coincide = texto1.regionMatches(
true, // ignorar mayúsculas/minúsculas
15, // índice de inicio en texto1
texto2, // segunda cadena
9, // índice de inicio en texto2
4 // número de caracteres a comparar
);
System.out.println(coincide); // true
Patrones de uso comunes
Validación de formatos
Se pueden combinar métodos de búsqueda y comparación para validar formatos específicos:
public boolean esFormatoFechaValido(String fecha) {
// Verificar formato DD/MM/AAAA
if (fecha.length() != 10) return false;
if (fecha.charAt(2) != '/' || fecha.charAt(5) != '/') return false;
try {
int dia = Integer.parseInt(fecha.substring(0, 2));
int mes = Integer.parseInt(fecha.substring(3, 5));
int anio = Integer.parseInt(fecha.substring(6));
return dia >= 1 && dia <= 31 && mes >= 1 && mes <= 12 && anio > 0;
} catch (NumberFormatException e) {
return false;
}
}
Búsqueda de patrones simples
public List<Integer> encontrarTodasLasOcurrencias(String texto, String patron) {
List<Integer> posiciones = new ArrayList<>();
int indice = 0;
while ((indice = texto.indexOf(patron, indice)) != -1) {
posiciones.add(indice);
indice += patron.length();
}
return posiciones;
}
Extracción de información estructurada
public Map<String, String> extraerParametrosURL(String url) {
Map<String, String> parametros = new HashMap<>();
int indiceQuery = url.indexOf('?');
if (indiceQuery == -1) return parametros;
String query = url.substring(indiceQuery + 1);
String[] pares = query.split("&");
for (String par : pares) {
int indiceIgual = par.indexOf('=');
if (indiceIgual != -1) {
String clave = par.substring(0, indiceIgual);
String valor = par.substring(indiceIgual + 1);
parametros.put(clave, valor);
}
}
return parametros;
}
Consideraciones de rendimiento
Al trabajar con métodos de búsqueda y comparación, se deben tener en cuenta algunas consideraciones de rendimiento:
- Los métodos
indexOf()
ycontains()
tienen una complejidad de O(n*m) donde n es la longitud de la cadena y m la longitud del patrón. - Para búsquedas complejas o múltiples en el mismo texto, se recomienda utilizar
Pattern
yMatcher
de la API de expresiones regulares. - Las comparaciones con
equalsIgnoreCase()
son más costosas que conequals()
debido a la conversión de caracteres.
// Para búsquedas repetitivas, compilar el patrón mejora el rendimiento
Pattern patron = Pattern.compile("Java", Pattern.CASE_INSENSITIVE);
Matcher matcher = patron.matcher(textoLargo);
while (matcher.find()) {
System.out.println("Encontrado en posición: " + matcher.start());
}
Métodos de transformación (toUpperCase, replace, split)
La clase String
en Java ofrece un conjunto de métodos de transformación que permiten modificar el contenido de las cadenas de texto. Debido a la inmutabilidad de los objetos String, estos métodos no alteran la cadena original, sino que devuelven una nueva cadena con las transformaciones aplicadas.
Conversión de mayúsculas y minúsculas
Los métodos toUpperCase()
y toLowerCase()
permiten convertir todos los caracteres de una cadena a mayúsculas o minúsculas respectivamente.
String texto = "Java Programming";
String mayusculas = texto.toUpperCase(); // "JAVA PROGRAMMING"
String minusculas = texto.toLowerCase(); // "java programming"
System.out.println(mayusculas);
System.out.println(minusculas);
Estos métodos son útiles para realizar comparaciones sin distinguir entre mayúsculas y minúsculas:
String entrada = "Java";
String busqueda = "java";
// Forma incorrecta (distingue mayúsculas/minúsculas)
if (entrada.equals(busqueda)) {
System.out.println("Coincidencia encontrada");
}
// Forma correcta (no distingue mayúsculas/minúsculas)
if (entrada.toLowerCase().equals(busqueda.toLowerCase())) {
System.out.println("Coincidencia encontrada");
}
También se puede especificar una configuración regional (Locale) para manejar correctamente caracteres específicos de diferentes idiomas:
String texto = "españa";
String mayusculasEspañol = texto.toUpperCase(Locale.forLanguageTag("es-ES")); // "ESPAÑA"
Reemplazo de caracteres y subcadenas
Java ofrece varios métodos para reemplazar caracteres o subcadenas dentro de un String.
replace()
El método replace()
tiene dos variantes:
replace(char oldChar, char newChar)
: Reemplaza todas las ocurrencias de un carácter por otro.replace(CharSequence target, CharSequence replacement)
: Reemplaza todas las ocurrencias de una subcadena por otra.
String texto = "Hello World";
// Reemplazo de caracteres
String textoModificado = texto.replace('l', 'L'); // "HeLLo WorLd"
// Reemplazo de subcadenas
String nuevoTexto = texto.replace("World", "Java"); // "Hello Java"
replaceFirst() y replaceAll()
Estos métodos utilizan expresiones regulares para realizar reemplazos más avanzados:
String codigo = "var x = 10; var y = 20;";
// Reemplaza solo la primera ocurrencia
String primerReemplazo = codigo.replaceFirst("var", "let"); // "let x = 10; var y = 20;"
// Reemplaza todas las ocurrencias
String todosReemplazos = codigo.replaceAll("var", "let"); // "let x = 10; let y = 20;"
El método replaceAll()
es muy potente cuando se combina con expresiones regulares:
String texto = "El precio es 15.99€ y el descuento es 3.50€";
// Reemplazar todos los números decimales por su valor redondeado
String resultado = texto.replaceAll("(\\d+)\\.\\d+", "$1");
System.out.println(resultado); // "El precio es 15€ y el descuento es 3€"
Casos de uso comunes
// Normalización de texto para búsquedas
String busqueda = " Machine Learning ";
String normalizado = busqueda.trim().toLowerCase().replace(" ", "-");
System.out.println(normalizado); // "machine-learning"
// Sanitización básica de entrada HTML
String comentarioUsuario = "<script>alert('XSS')</script>Buen artículo!";
String seguro = comentarioUsuario.replace("<", "<").replace(">", ">");
System.out.println(seguro); // "<script>alert('XSS')</script>Buen artículo!"
División de cadenas con split()
El método split(String regex)
divide una cadena en un array de subcadenas utilizando una expresión regular como delimitador.
String frase = "Java es un lenguaje de programación versátil";
String[] palabras = frase.split(" ");
// Resultado: ["Java", "es", "un", "lenguaje", "de", "programación", "versátil"]
for (String palabra : palabras) {
System.out.println(palabra);
}
Se puede limitar el número de divisiones especificando un segundo parámetro:
String datos = "Juan,25,Madrid,Ingeniero";
String[] campos = datos.split(",", 3);
// Resultado: ["Juan", "25", "Madrid,Ingeniero"]
for (String campo : campos) {
System.out.println(campo);
}
Consideraciones con caracteres especiales
Cuando se utilizan caracteres que tienen significado especial en expresiones regulares (como .
, *
, +
, ?
, etc.), se deben escapar con \\
:
String ruta = "C:\\Documentos\\Java\\ejemplos.txt";
String[] partes = ruta.split("\\\\");
// Resultado: ["C:", "Documentos", "Java", "ejemplos.txt"]
Aplicaciones prácticas
El método split()
es fundamental para procesar datos estructurados:
// Procesamiento de CSV
String linea = "Juan,Pérez,30,Madrid";
String[] datos = linea.split(",");
String nombre = datos[0];
String apellido = datos[1];
int edad = Integer.parseInt(datos[2]);
String ciudad = datos[3];
// Análisis de logs
String logEntry = "2023-05-15 14:30:45 [ERROR] Connection refused: 192.168.1.1:8080";
String[] partes = logEntry.split(" ", 4);
String fecha = partes[0];
String hora = partes[1];
String nivel = partes[2].replace("[", "").replace("]", "");
String mensaje = partes[3];
Combinación de métodos de transformación
Los métodos de transformación se pueden encadenar para realizar operaciones complejas en una sola línea:
String entrada = " usuario@DOMINIO.com ";
String normalizado = entrada.trim() // Eliminar espacios
.toLowerCase() // Convertir a minúsculas
.replace(" ", ""); // Eliminar espacios internos
System.out.println(normalizado); // "usuario@dominio.com"
Un ejemplo más complejo que combina varios métodos:
public String formatearNombreArchivo(String nombreOriginal) {
return nombreOriginal.trim()
.toLowerCase()
.replaceAll("[áàäâã]", "a")
.replaceAll("[éèëê]", "e")
.replaceAll("[íìïî]", "i")
.replaceAll("[óòöôõ]", "o")
.replaceAll("[úùüû]", "u")
.replaceAll("[^a-z0-9\\.]", "-")
.replaceAll("-+", "-");
}
// Uso:
String archivo = " Currículum Vitáe (2023).pdf ";
String formateado = formatearNombreArchivo(archivo);
// Resultado: "curriculum-vitae-2023.pdf"
Procesamiento de texto estructurado
La combinación de split()
con otros métodos de transformación permite procesar texto estructurado:
String json = "{\"nombre\":\"Juan\",\"edad\":30,\"ciudad\":\"Madrid\"}";
// Extraer pares clave-valor de forma básica
String contenido = json.substring(1, json.length() - 1); // Eliminar llaves
String[] pares = contenido.split(",");
Map<String, String> datos = new HashMap<>();
for (String par : pares) {
String[] keyValue = par.split(":");
String clave = keyValue[0].replace("\"", "");
String valor = keyValue[1].replace("\"", "");
datos.put(clave, valor);
}
System.out.println("Nombre: " + datos.get("nombre"));
System.out.println("Edad: " + datos.get("edad"));
Consideraciones de rendimiento
Al trabajar con transformaciones de cadenas, se deben tener en cuenta algunas consideraciones:
- Las operaciones de transformación crean nuevos objetos String, lo que puede afectar al rendimiento si se realizan muchas operaciones consecutivas.
- Para transformaciones múltiples, es más eficiente utilizar
StringBuilder
:
// Enfoque ineficiente
String resultado = "";
for (int i = 0; i < 1000; i++) {
resultado += "a";
}
// Enfoque eficiente
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("a");
}
String resultado = sb.toString();
- El método
split()
con expresiones regulares complejas puede ser costoso para cadenas muy largas. En estos casos, se puede considerar el uso deStringTokenizer
oScanner
como alternativas.
Rendimiento y optimización con String, StringBuilder y StringBuffer
Java ofrece tres clases principales para el manejo de texto, cada una con características específicas que afectan directamente al rendimiento de nuestras aplicaciones.
Inmutabilidad de String y sus implicaciones
La clase String
en Java es inmutable, lo que significa que una vez creado un objeto String, su contenido no puede ser modificado. Cada operación que parece modificar una cadena en realidad crea un nuevo objeto String.
String nombre = "Java";
nombre = nombre + " Programming"; // Crea un nuevo objeto String
Esta inmutabilidad tiene consecuencias:
- Seguridad: Los objetos String pueden compartirse de forma segura entre diferentes partes de una aplicación.
- Hashcode: El valor hash de un String se calcula una sola vez y se almacena en caché.
- Pool de Strings: Java mantiene un pool de Strings para reutilizar objetos con el mismo contenido.
Sin embargo, la inmutabilidad también presenta desafíos de rendimiento:
String resultado = "";
for (int i = 0; i < 10000; i++) {
resultado += i; // Crea 10000 objetos String intermedios
}
Este código crea miles de objetos String temporales que deben ser recolectados por el garbage collector, lo que afecta negativamente al rendimiento.
StringBuilder: la alternativa mutable
La clase StringBuilder
fue diseñada específicamente para abordar las limitaciones de rendimiento de String cuando se realizan múltiples modificaciones. A diferencia de String, StringBuilder
es mutable, lo que significa que puede modificar su contenido sin crear nuevos objetos.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // Modifica el mismo objeto StringBuilder
}
String resultado = sb.toString(); // Conversión final a String
Los métodos más utilizados de StringBuilder incluyen:
- append(): Añade contenido al final del StringBuilder.
- insert(): Inserta contenido en una posición específica.
- delete(): Elimina caracteres en un rango determinado.
- replace(): Reemplaza caracteres en un rango por una nueva cadena.
- reverse(): Invierte el contenido del StringBuilder.
StringBuilder builder = new StringBuilder("Hola");
builder.append(" Mundo") // "Hola Mundo"
.insert(0, "¡") // "¡Hola Mundo"
.append("!") // "¡Hola Mundo!"
.delete(5, 10) // "¡Hola!"
.replace(1, 5, "Java"); // "¡Java!"
String resultado = builder.toString(); // Conversión final a String
StringBuffer: la alternativa thread-safe
StringBuffer
ofrece la misma funcionalidad que StringBuilder, pero con una diferencia crucial: es thread-safe. Todos sus métodos están sincronizados, lo que garantiza la seguridad en entornos multihilo.
StringBuffer buffer = new StringBuffer();
// Seguro para usar en múltiples hilos
buffer.append("Thread-safe ");
buffer.append("string manipulation");
La sincronización tiene un costo en términos de rendimiento, por lo que StringBuffer es más lento que StringBuilder en aplicaciones de un solo hilo.
Comparativa de rendimiento
Para entender mejor las diferencias de rendimiento, veamos una comparación práctica:
// Medición con String
long inicio = System.nanoTime();
String s = "";
for (int i = 0; i < 100000; i++) {
s += "a";
}
long fin = System.nanoTime();
System.out.println("String: " + (fin - inicio) / 1000000 + " ms");
// Medición con StringBuilder
inicio = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("a");
}
String resultadoSb = sb.toString();
fin = System.nanoTime();
System.out.println("StringBuilder: " + (fin - inicio) / 1000000 + " ms");
// Medición con StringBuffer
inicio = System.nanoTime();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < 100000; i++) {
sbf.append("a");
}
String resultadoSbf = sbf.toString();
fin = System.nanoTime();
System.out.println("StringBuffer: " + (fin - inicio) / 1000000 + " ms");
Los resultados típicos muestran que StringBuilder es más rápido que String para operaciones múltiples, y StringBuffer es ligeramente más lento que StringBuilder debido a la sincronización.
Optimización de la capacidad inicial
Tanto StringBuilder como StringBuffer permiten especificar una capacidad inicial, lo que puede mejorar el rendimiento cuando se conoce aproximadamente el tamaño final de la cadena:
// Sin capacidad inicial especificada
StringBuilder sb1 = new StringBuilder();
// Con capacidad inicial de 1000 caracteres
StringBuilder sb2 = new StringBuilder(1000);
Cuando no se especifica una capacidad, se utiliza un valor predeterminado (generalmente 16 caracteres). Si las operaciones de append exceden esta capacidad, se debe realizar una expansión interna (realocar memoria y copiar el contenido), lo que afecta al rendimiento.
// Optimización para concatenar 1000 números
StringBuilder optimizado = new StringBuilder(5000); // Estimación del tamaño final
for (int i = 0; i < 1000; i++) {
optimizado.append(i).append(", ");
}
Cuándo usar cada clase
La elección entre String, StringBuilder y StringBuffer depende del contexto específico:
- String: Ideal para cadenas que no cambiarán o cambiarán muy poco. También es la mejor opción cuando se necesita pasar cadenas entre métodos o almacenarlas en colecciones.
- StringBuilder: La mejor opción para manipulaciones intensivas de cadenas en un entorno de un solo hilo. Ofrece el mejor rendimiento para concatenaciones y modificaciones.
- StringBuffer: Necesario cuando se manipulan cadenas en un entorno multihilo donde varios hilos pueden modificar la misma cadena simultáneamente.
Patrones de optimización comunes
Concatenación en bucles
// Mal (crea muchos objetos String)
String resultado = "";
for (String item : items) {
resultado += item + ", ";
}
// Bien (usa un solo StringBuilder)
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item).append(", ");
}
String resultado = sb.toString();
Construcción de consultas SQL
// Construcción eficiente de una consulta SQL dinámica
StringBuilder query = new StringBuilder("SELECT * FROM usuarios WHERE 1=1");
if (nombre != null) {
query.append(" AND nombre = '").append(nombre).append("'");
}
if (edad > 0) {
query.append(" AND edad > ").append(edad);
}
String sqlFinal = query.toString();
Generación de documentos
// Generación eficiente de un documento HTML
StringBuilder html = new StringBuilder(1024); // Capacidad inicial estimada
html.append("<!DOCTYPE html>\n")
.append("<html>\n")
.append(" <head>\n")
.append(" <title>").append(titulo).append("</title>\n")
.append(" </head>\n")
.append(" <body>\n")
.append(" <h1>").append(encabezado).append("</h1>\n");
// Añadir contenido dinámico
for (String parrafo : parrafos) {
html.append(" <p>").append(parrafo).append("</p>\n");
}
html.append(" </body>\n")
.append("</html>");
String documentoFinal = html.toString();
Optimizaciones avanzadas
Reutilización de StringBuilder
En operaciones repetitivas, se puede reutilizar el mismo objeto StringBuilder para evitar crear instancias innecesarias:
StringBuilder reutilizable = new StringBuilder(100);
List<String> resultados = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
reutilizable.setLength(0); // Vacía el StringBuilder sin crear uno nuevo
reutilizable.append("Prefijo-").append(i).append("-Sufijo");
resultados.add(reutilizable.toString());
}
Concatenación de cadenas en expresiones
El compilador de Java optimiza automáticamente algunas concatenaciones simples de cadenas utilizando StringBuilder internamente:
// El compilador optimiza esto automáticamente usando StringBuilder
String mensaje = "Hola " + nombre + ", tienes " + edad + " años.";
Sin embargo, esta optimización no se aplica en bucles o expresiones complejas, donde se debe usar StringBuilder explícitamente.
Uso de String.join() para concatenaciones simples
Para concatenaciones simples con un delimitador común, Java ofrece el método String.join()
que es más legible y eficiente que la concatenación manual:
// Más eficiente y legible que concatenación manual
String[] palabras = {"Java", "es", "un", "lenguaje", "de", "programación"};
String frase = String.join(" ", palabras);
// También funciona con colecciones
List<String> nombres = List.of("Ana", "Juan", "María");
String listaDeNombres = String.join(", ", nombres);
Consideraciones de memoria
Además del rendimiento en tiempo de ejecución, es importante considerar el uso de memoria:
- String: Cada operación crea un nuevo objeto, lo que puede llevar a un alto consumo de memoria si se realizan muchas operaciones.
- StringBuilder/StringBuffer: Utilizan un buffer interno que puede crecer según sea necesario, lo que generalmente resulta en un menor consumo de memoria para operaciones múltiples.
// Monitoreo básico de memoria
Runtime runtime = Runtime.getRuntime();
long memoriaAntes = runtime.totalMemory() - runtime.freeMemory();
// Operaciones con cadenas...
long memoriaDespues = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Memoria utilizada: " + (memoriaDespues - memoriaAntes) / 1024 + " KB");
Formateo de Strings
El formateo de cadenas permite crear textos estructurados combinando valores variables con plantillas predefinidas. Java ofrece diversas técnicas para dar formato a las cadenas, desde métodos tradicionales hasta enfoques más modernos, cada uno con sus propias ventajas en diferentes contextos.
Método String.format()
El método String.format()
proporciona una forma flexible de crear cadenas formateadas utilizando especificadores de formato similares a los de C. Este método acepta una cadena de formato y una serie de argumentos que se incorporan en la cadena resultante.
String nombre = "Ana";
int edad = 28;
double salario = 2500.75;
String mensaje = String.format("Empleado: %s, Edad: %d, Salario: %.2f€", nombre, edad, salario);
System.out.println(mensaje); // Empleado: Ana, Edad: 28, Salario: 2500,75€
Los especificadores de formato más comunes incluyen:
%s
- Para cadenas%d
- Para enteros%f
- Para números de punto flotante%b
- Para valores booleanos%c
- Para caracteres%t
- Para fechas y horas (con subformatos)%%
- Para imprimir el símbolo %
Se pueden aplicar modificadores a estos especificadores para controlar con precisión la salida:
// Ancho y alineación
String alineado = String.format("|%-10s|%10s|", "izquierda", "derecha");
System.out.println(alineado); // |izquierda | derecha|
// Precisión decimal
double pi = Math.PI;
String piFormateado = String.format("Pi con 2 decimales: %.2f", pi);
System.out.println(piFormateado); // Pi con 2 decimales: 3,14
// Formato de números con separadores de miles
long poblacion = 47_350_000;
String pobFormateada = String.format("Población: %,d habitantes", poblacion);
System.out.println(pobFormateada); // Población: 47.350.000 habitantes
Formateo de fechas y horas
El formateo de fechas y horas merece especial atención por su complejidad. El especificador %t
se combina con letras adicionales para controlar el formato:
LocalDateTime ahora = LocalDateTime.now();
String fechaFormateada = String.format("Fecha: %tD, Hora: %tT", ahora, ahora);
System.out.println(fechaFormateada); // Fecha: 05/15/23, Hora: 14:30:45
// Formatos más específicos
String formatoCompleto = String.format(
"Día: %td, Mes: %tB, Año: %tY, Hora: %tH:%tM",
ahora, ahora, ahora, ahora, ahora
);
Para evitar repetir la referencia al mismo objeto, se puede usar el índice de argumento:
String formatoMejorado = String.format(
"Fecha: %1$td/%1$tm/%1$tY, Hora: %1$tH:%1$tM:%1$tS",
ahora
);
Método printf() para salida formateada
El método System.out.printf()
utiliza la misma sintaxis de formateo que String.format()
, pero imprime directamente en la salida estándar en lugar de devolver una cadena:
double precio = 19.99;
int cantidad = 3;
System.out.printf("Subtotal: %.2f€ × %d unidades = %.2f€%n",
precio, cantidad, precio * cantidad);
// Subtotal: 19,99€ × 3 unidades = 59,97€
El especificador %n
se utiliza para insertar un salto de línea independiente de la plataforma, lo que es preferible a usar \n
directamente.
Formateo con String.formatted() (Java 15+)
A partir de Java 15, se introdujo el método de instancia formatted()
que simplifica el formateo al permitir llamarlo directamente sobre una cadena de formato:
String plantilla = "Usuario: %s (ID: %d)";
String resultado = plantilla.formatted("carlos_dev", 12345);
System.out.println(resultado); // Usuario: carlos_dev (ID: 12345)
Este enfoque es más conciso y mejora la legibilidad del código en muchos casos.
Formateo con StringBuilder
Para escenarios donde se necesita construir cadenas formateadas de manera dinámica, StringBuilder
ofrece métodos de formateo similares:
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
formatter.format("Lista de precios:%n");
String[] productos = {"Teclado", "Ratón", "Monitor"};
double[] precios = {45.99, 22.50, 199.99};
for (int i = 0; i < productos.length; i++) {
formatter.format("- %-10s %8.2f€%n", productos[i], precios[i]);
}
System.out.println(sb.toString());
// Lista de precios:
// - Teclado 45,99€
// - Ratón 22,50€
// - Monitor 199,99€
Plantillas de texto (Java 21+)
Java 21 introdujo las plantillas de texto (Text Blocks con interpolación), una característica que simplifica el formateo de cadenas:
String nombre = "María";
int puntuacion = 95;
String mensaje = STR."¡Felicidades \{nombre}! Has obtenido \{puntuacion} puntos.";
System.out.println(mensaje); // ¡Felicidades María! Has obtenido 95 puntos.
Las plantillas de texto permiten la interpolación de expresiones directamente en la cadena, lo que resulta en un código más legible y menos propenso a errores que los métodos tradicionales de formateo.
int x = 10;
int y = 20;
String operacion = STR."\{x} + \{y} = \{x + y}";
System.out.println(operacion); // 10 + 20 = 30
// Expresiones más complejas
LocalDate fecha = LocalDate.now();
String informe = STR."""
Informe generado el \{fecha.format(DateTimeFormatter.ISO_LOCAL_DATE)}
Temperatura: \{String.format("%.1f", 22.75)}°C
Estado: \{Math.random() > 0.5 ? "Óptimo" : "Requiere revisión"}
""";
MessageFormat para localización
La clase MessageFormat
es útil para crear mensajes localizados y parametrizados:
String patron = "El archivo {0} tiene {1,number,integer} bytes y fue modificado el {2,date,long}.";
Object[] valores = {"config.xml", 1234, new Date()};
String mensaje = MessageFormat.format(patron, valores);
System.out.println(mensaje);
// El archivo config.xml tiene 1.234 bytes y fue modificado el 15 de mayo de 2023.
MessageFormat
se integra con el sistema de internacionalización de Java, permitiendo adaptar los mensajes a diferentes idiomas y convenciones regionales:
// Formateo según configuración regional
MessageFormat mf = new MessageFormat("El precio es {0,number,currency}.", new Locale("es", "ES"));
String precioEspañol = mf.format(new Object[]{29.99});
System.out.println(precioEspañol); // El precio es 29,99 €.
mf = new MessageFormat("The price is {0,number,currency}.", Locale.US);
String precioUSA = mf.format(new Object[]{29.99});
System.out.println(precioUSA); // The price is $29.99.
Formateo de números con DecimalFormat
Para un control más preciso sobre el formato de números, la clase DecimalFormat
ofrece opciones avanzadas:
// Formato de porcentajes
DecimalFormat dfPorcentaje = new DecimalFormat("##0.00'%'");
System.out.println(dfPorcentaje.format(0.7625)); // 76,25%
// Formato de moneda personalizado
DecimalFormat dfMoneda = new DecimalFormat("###,###.## €");
System.out.println(dfMoneda.format(1250.75)); // 1.250,75 €
// Números con prefijos y sufijos
DecimalFormat dfTamaño = new DecimalFormat("### KB");
System.out.println(dfTamaño.format(512)); // 512 KB
Los patrones de formato en DecimalFormat
utilizan símbolos especiales:
0
- Dígito obligatorio#
- Dígito opcional.
- Separador decimal,
- Separador de agrupación'
- Escape para texto literal%
- Multiplicar por 100 y mostrar como porcentaje‰
- Multiplicar por 1000 y mostrar como por mil
// Diferentes patrones para el mismo número
double valor = 12345.6789;
String[] patrones = {"###,###.###", "000,000.000", "#,##0.00", "0.0"};
for (String patron : patrones) {
DecimalFormat df = new DecimalFormat(patron);
System.out.println(patron + ": " + df.format(valor));
}
// ###,###.###: 12.345,679
// 000,000.000: 012.345,679
// #,##0.00: 12.345,68
// 0.0: 12345,7
Casos de uso prácticos
Generación de informes tabulares
String[] encabezados = {"Producto", "Precio", "Stock", "Valor"};
Object[][] datos = {
{"Laptop", 899.99, 15, 899.99 * 15},
{"Smartphone", 499.50, 30, 499.50 * 30},
{"Auriculares", 79.99, 50, 79.99 * 50}
};
// Formato de tabla
System.out.printf("| %-15s | %10s | %5s | %12s |%n",
(Object[])encabezados);
System.out.println("|-----------------|------------|-------|--------------|");
for (Object[] fila : datos) {
System.out.printf("| %-15s | %,10.2f€ | %5d | %,12.2f€ |%n",
fila[0], fila[1], fila[2], fila[3]);
}
Formateo de datos científicos
double[] mediciones = {0.0000123, 4.567e6, 2.998e8};
String[] unidades = {"s", "Hz", "m/s"};
for (int i = 0; i < mediciones.length; i++) {
// Formato científico
System.out.printf("Valor: %e %s%n", mediciones[i], unidades[i]);
// Formato adaptativo
System.out.printf("Valor: %g %s%n", mediciones[i], unidades[i]);
}
Generación de URLs y consultas
String baseUrl = "https://api.ejemplo.com/buscar";
String query = "java programación";
int pagina = 2;
int resultadosPorPagina = 25;
String url = String.format("%s?q=%s&page=%d&size=%d",
baseUrl,
URLEncoder.encode(query, StandardCharsets.UTF_8),
pagina,
resultadosPorPagina);
System.out.println(url);
Consideraciones de rendimiento
El formateo de cadenas tiene implicaciones de rendimiento que deben considerarse en aplicaciones críticas:
String.format()
es conveniente pero relativamente costoso en términos de rendimiento debido a la necesidad de analizar la cadena de formato y convertir los argumentos.- Para operaciones de formateo intensivas dentro de bucles, considere reutilizar un
Formatter
oStringBuilder
. - Las plantillas de texto (Java 21+) ofrecen un rendimiento mejorado en comparación con
String.format()
para muchos casos de uso.
// Comparación de rendimiento básica
long inicio, fin;
inicio = System.nanoTime();
for (int i = 0; i < 10000; i++) {
String.format("%d + %d = %d", i, i+1, i*2+1);
}
fin = System.nanoTime();
System.out.println("String.format: " + (fin - inicio) / 1_000_000 + " ms");
// Con Java 21+
inicio = System.nanoTime();
for (int i = 0; i < 10000; i++) {
STR."\{i} + \{i+1} = \{i*2+1}";
}
fin = System.nanoTime();
System.out.println("Plantillas de texto: " + (fin - inicio) / 1_000_000 + " ms");
Buenas prácticas
- Elija el método adecuado según el contexto:
String.format()
para casos generales, plantillas de texto para código más legible, yMessageFormat
para contenido localizable. - Reutilice patrones de formato cuando sea posible para mejorar el rendimiento.
- Considere la localización desde el principio si su aplicación será utilizada internacionalmente.
- Valide los datos de entrada antes de formatearlos para evitar excepciones inesperadas.
- Utilice constantes para patrones de formato complejos o reutilizados.
// Buena práctica: constantes para patrones reutilizados
private static final String FORMATO_MONEDA = "%,.2f€";
private static final String FORMATO_FECHA = "%1$td/%1$tm/%1$tY";
private static final DecimalFormat FORMATO_PORCENTAJE = new DecimalFormat("##0.00'%'");
// Uso
String precio = String.format(FORMATO_MONEDA, 1299.99);
String fecha = String.format(FORMATO_FECHA, LocalDate.now());
String tasa = FORMATO_PORCENTAJE.format(0.2175);
Aprendizajes de esta lección
- Comprender la inmutabilidad de los objetos String en Java
- Usar el método
length()
para medir la longitud de una cadena - Acceder a caracteres específicos con
charAt()
- Extraer subcadenas utilizando
substring()
- Manejar excepciones comunes como
StringIndexOutOfBoundsException
- Aplicar estos métodos para procesar y validar cadenas de texto
Completa Java y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs