Java
Tutorial Java: Métodos de la clase String
Java métodos String: manipulación de cadenas. Domina la manipulación de cadenas en Java utilizando métodos de String con ejemplos detallados.
Aprende Java y certifícateMé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.
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);
Otros ejercicios de programación de Java
Evalúa tus conocimientos de esta lección Métodos de la clase String con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Streams: match
Gestión de errores y excepciones
CRUD en Java de modelo Customer sobre un ArrayList
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
API java.nio 2
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
CRUD en Java de modelo Customer sobre un HashMap
Interfaces
Enumeraciones Enums
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
CRUD de productos en Java
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Herencia
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Mapas
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Arrays Y Matrices
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Transformación
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Mapeo
Programación Funcional
Streams Paralelos
Programación Funcional
Agrupación Y Partición
Programación Funcional
Filtrado Y Búsqueda
Programación Funcional
Api Java.nio 2
Entrada Y Salida Io
Fundamentos De Io
Entrada Y Salida Io
Leer Y Escribir Archivos
Entrada Y Salida Io
Httpclient Moderno
Entrada Y Salida Io
Clases De Nio2
Entrada Y Salida Io
Api Java.time
Api Java.time
Localtime
Api Java.time
Localdatetime
Api Java.time
Localdate
Api Java.time
Executorservice
Concurrencia
Virtual Threads (Project Loom)
Concurrencia
Future Y Completablefuture
Concurrencia
Spring Framework
Frameworks Para Java
Micronaut
Frameworks Para Java
Maven
Frameworks Para Java
Gradle
Frameworks Para Java
Lombok Para Java
Frameworks Para Java
Quarkus
Frameworks Para Java
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Introducción A Junit 5
Testing
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje 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