UD2 · Sección 3 — La clase String en profundidad
| Dato | Valor |
|---|---|
| Unidad Didáctica | UD2 — Programación Orientada a Objetos con la API de Java |
| Resultado de Aprendizaje | RA2 — Escribe y prueba programas sencillos, reconociendo y aplicando los fundamentos de la programación orientada a objetos. |
| Sección | 3 de 10 — La clase String en profundidad |
| Evaluación | 1.ª Evaluación |
| Duración estimada | 3 horas lectivas |
| Criterios de evaluación cubiertos | CE 2.2 (escribe programas simples), CE 2.3 (instancia objetos a partir de clases predefinidas), CE 2.4 (utiliza métodos y propiedades), CE 2.5 (llamadas a métodos estáticos), CE 2.6 (utiliza parámetros), CE 2.8 (utiliza constructores) |
| Prerrequisitos | UD1 completa, UD2 Secciones 1 y 2 |
| Paquete del código de ejemplo | dam.programacion.ud2 |
| Herramientas | JDK 25, IntelliJ IDEA Community, Maven 3.9.x |
1. Punto de partida
String es la clase que más vas a usar en toda tu carrera como programador Java. Literalmente: cada programa que escribas manipulará texto de alguna forma, y casi todo ese texto pasará por un String. Por eso esta sección es más larga y densa que las anteriores: merece la pena invertir tiempo en conocerla a fondo.
Hasta ahora has usado String como una caja negra: escribías literales entre comillas, los concatenabas con + y los mostrabas por consola. Ahora vamos a abrir la caja y ver qué hay dentro. Vas a descubrir dos cosas: una, que String tiene una particularidad muy importante (es inmutable) que condiciona todo su comportamiento. Dos, que tiene casi cincuenta métodos públicos que resuelven la mayoría de problemas de manipulación de texto sin que tengas que escribir lógica manual.
Al terminar esta sección serás capaz de buscar dentro de un texto, transformarlo, extraer partes, compararlo y formatearlo sin recurrir a bucles manuales. Es decir: dejarás de "pelear" con los strings y empezarás a "trabajar" con ellos.
2. Inmutabilidad y string interning
2.1. Inmutabilidad: qué significa
Un String es inmutable: una vez creado, su contenido no puede cambiar. Cualquier método que parezca modificar un String (toUpperCase(), replace(), trim()) en realidad devuelve un String nuevo y deja el original intacto.
Analogía del libro de piedra. Imagina que cada String es un libro tallado en piedra. Si quieres "cambiarlo" a mayúsculas, no puedes modificar la piedra: necesitas tallar un libro nuevo con el texto en mayúsculas. El original sigue existiendo, intacto.
2.2. Por qué Java lo diseñó así
La inmutabilidad tiene tres ventajas clave:
- Seguridad: si un
Stringse pasa como parámetro a un método, el método no puede modificarlo. Esto es crítico en contraseñas, URLs, nombres de fichero. - Eficiencia en concurrencia: varios hilos pueden leer el mismo
Stringsin bloqueos, porque nadie puede cambiarlo. - Posibilidad de pooling: como los strings no cambian, Java puede reutilizarlos (lo veremos a continuación).
2.3. String interning: el string pool
Como ya vimos en la Sección 1, Java mantiene una zona especial del heap llamada string pool. Cuando escribes un literal como "Maria", Java lo añade al pool; si más adelante escribes otro "Maria", reutiliza el mismo objeto en vez de crear uno nuevo.
Trampa. Usar new String("Maria") es casi siempre un error: crea un objeto en el heap fuera del pool y desperdicia memoria. Usa siempre literales o métodos que devuelvan String.
2.4. Método intern()
Si por algún motivo tienes un String fuera del pool (por ejemplo, uno creado con new o uno leído de un fichero) y quieres meterlo en el pool, puedes llamar a intern(). Devuelve una referencia al objeto equivalente del pool (o lo añade si no existe). Es avanzado y rara vez se usa; lo mencionamos para que no te pille por sorpresa si lo ves en código ajeno.
flowchart TB
L["Literal 'Maria'"] --> P["String Pool"]
P --> A["Variable a"]
P --> B["Variable b"]
N["new String('Maria')"] --> H["Heap (fuera del pool)"]
H --> C["Variable c"]
P -.intern().-> H
3. Métodos de búsqueda
Estos métodos no modifican el String original; solo devuelven información sobre él.
| Método | Qué hace | Devuelve |
|---|---|---|
int length() |
Longitud de la cadena | int |
boolean isEmpty() |
¿Está vacía? (longitud 0) | boolean |
boolean isBlank() |
¿Está en blanco? (longitud 0 o solo espacios) | boolean |
boolean contains(CharSequence) |
¿Contiene la subcadena indicada? | boolean |
boolean startsWith(String) |
¿Empieza por...? | boolean |
boolean endsWith(String) |
¿Termina por...? | boolean |
int indexOf(String) |
Posición de la primera aparición | int (-1 si no existe) |
int lastIndexOf(String) |
Posición de la última aparición | int (-1 si no existe) |
char charAt(int) |
Carácter en la posición indicada | char |
Ejemplos:
Trampa de isBlank() vs isEmpty():
isBlank() se añadió en Java 11. Para Java 25 está totalmente disponible.
Rangos de índices
Los índices en Java empiezan en 0. Para una cadena de longitud n, los índices válidos van de 0 a n-1. Pasar un índice fuera de rango a charAt lanza StringIndexOutOfBoundsException.
4. Métodos de transformación
Estos métodos devuelven un nuevo String; el original no cambia.
| Método | Qué hace |
|---|---|
String toUpperCase() |
Convierte a mayúsculas |
String toLowerCase() |
Convierte a minúsculas |
String trim() |
Elimina espacios al inicio y al final (solo ASCII <= U+0020) |
String strip() |
Como trim() pero Unicode completo (recomendado desde Java 11) |
String stripLeading() |
Elimina espacios solo al inicio |
String stripTrailing() |
Elimina espacios solo al final |
String replace(char, char) |
Reemplaza todas las apariciones de un char por otro |
String replace(CharSequence, CharSequence) |
Reemplaza subcadenas |
String replaceAll(String regex, String repl) |
Reemplaza usando expresiones regulares |
String replaceFirst(String regex, String repl) |
Reemplaza solo la primera aparición (regex) |
String repeat(int) |
Repite la cadena n veces (Java 11+) |
Ejemplos:
trim() vs strip()
trim() es histórico (existe desde Java 1.0) y solo elimina caracteres ASCII con código <= U+0020 (espacio, tab, salto de línea, retorno de carro). strip() se añadió en Java 11 y usa la definición Unicode de "espacio en blanco", que incluye caracteres como el espacio inseparable (\u00A0) o espacios de anchura completa de los idiomas asiáticos (\u3000). En código nuevo, usa siempre strip().
5. Extracción: substring, split y chars
5.1. substring
Devuelve una porción de la cadena original. beginIndex es inclusivo, endIndex es exclusivo (es decir, el carácter en endIndex no se incluye).
Analogía del pan. Imagina que el String es una barra de pan y substring es el cuchillo. substring(0, 4) corta desde el inicio hasta la cuarta marca (sin incluirla). La barra original sigue entera; el cuchillo solo te entrega una rebanada.
5.2. split
Divide la cadena en un array de subcadenas usando un separador (expresión regular). Devuelve un String[], que es un array de strings.
Trampa: el separador es una regex. Si usas . como separador, no funciona como esperas:
Por ahora basta con saber que si tu separador contiene caracteres especiales (. * + ? | ( ) [ { ^ $), debes escaparlos con \\. Las expresiones regulares en sí se verán más adelante en el módulo.
5.3. chars
Devuelve un IntStream con los códigos Unicode de cada carácter. Es un adelanto de la Streams API, que se verá a fondo en la UD6. Aquí lo mencionamos para que lo reconozcas si lo ves:
6. Construcción: String.join, .formatted, .repeat
Ya vimos repeat en la sección de transformación. Aquí veremos otras dos formas de construir strings nuevas a partir de piezas.
6.1. String.join (método estático)
Es la operación inversa de split: une varios strings con un separador. Es un método estático de la clase String.
6.2. String.format y .formatted (métodos estático y de instancia)
Ya vistos en UD1, los recordamos aquí porque encajan temáticamente. Permiten construir un string a partir de una plantilla con marcadores.
Especificadores más usados:
| Especificador | Tipo | Ejemplo |
|---|---|---|
%s |
String (cualquier objeto con toString()) |
"Hola, %s".formatted("Maria") |
%d |
Entero (int, long) | "Edad: %d".formatted(23) |
%f |
Decimal (float, double) | "PI: %f".formatted(Math.PI) |
%n |
Salto de línea independiente del SO | "Linea 1%nLinea 2".formatted() |
%.2f |
Decimal con 2 decimales | "%.2f".formatted(3.14159) → "3.14" |
%05d |
Entero con ceros a la izquierda (5 dígitos) | "%05d".formatted(42) → "00042" |
7. Comparación correcta: equals, equalsIgnoreCase, compareTo
7.1. equals y equalsIgnoreCase
Comparan el contenido. equals distingue mayúsculas de minúsculas; equalsIgnoreCase no.
Regla de oro: usa siempre .equals() para comparar strings por contenido. Nunca ==.
7.2. compareTo y compareToIgnoreCase
Devuelve:
- Un número negativo si este string va antes que
otro(en orden lexicográfico). - 0 si son iguales.
- Un número positivo si este string va después que
otro.
Es el equivalente a los operadores <, ==, > pero para strings.
Casos típicos: ordenar alfabéticamente una lista de nombres (se verá en UD6 con List.sort), verificar si una contraseña está dentro de un rango de seguridad.
7.3. Tabla resumen de comparación
| Método | Qué compara | Devuelve | ¿Sensible a mayúsculas? |
|---|---|---|---|
== |
Referencias (dirección de memoria) | boolean |
— |
.equals(String) |
Contenido | boolean |
Sí |
.equalsIgnoreCase(String) |
Contenido | boolean |
No |
.compareTo(String) |
Orden lexicográfico | int (neg/0/pos) |
Sí |
.compareToIgnoreCase(String) |
Orden lexicográfico | int (neg/0/pos) |
No |
8. Comprobación: isEmpty, isBlank
Ya los mencionamos en la sección de búsqueda, pero conviene recordarlos como categoría aparte porque se usan muchísimo en validaciones:
En validación de formularios (que verás más adelante en el curso), isBlank() suele ser la opción correcta: "no me valen ni la cadena vacía ni la cadena solo con espacios".
9. Text blocks: literales multilínea (Java 15+)
Ya introducidos en UD1, los recordamos porque encajan en el temario de String. Permiten escribir strings de varias líneas sin escapar comillas ni saltos de línea.
Reglas básicas:
- Empiezan con
"""seguido de un salto de línea. - Terminan con
"""en su propia línea. - La indentación se ajusta automáticamente a la del cierre
""".
Son ideales para HTML, JSON, SQL, mensajes largos.
10. Ejemplo integrador
Programa que combina los conceptos de la sección. Imagina que estás construyendo un sistema que valida y normaliza correos electrónicos.
Salida esperada:
Antes de ejecutarlo, intenta predecir la salida. Si aciertas, has entendido la sección.
11. Errores comunes
| Error | Qué pasa | Cómo evitarlo |
|---|---|---|
Usar == para comparar strings |
Compara referencias, no contenido | Usa siempre .equals() o .equalsIgnoreCase() |
Olvidar que String es inmutable |
Llamar a s.toUpperCase(); y esperar que s cambie |
Asigna el resultado: s = s.toUpperCase(); |
Confundir substring inclusivo/exclusivo |
substring(0, 4) devuelve 4 caracteres, no 5 |
Recuerda: endIndex es exclusivo |
Usar split(".") directamente |
Devuelve array vacío (el punto es regex) | Escapa: split("\\.") |
Confundir trim() con strip() |
trim no elimina espacios Unicode raros |
Usa strip() en código nuevo |
Pasar índices fuera de rango a charAt |
StringIndexOutOfBoundsException |
Comprueba length() antes |
Usar new String("...") |
Desperdicia memoria (crea fuera del pool) | Usa literales directamente |
12. Resumen
| Categoría | Métodos clave |
|---|---|
| Búsqueda | length, isEmpty, isBlank, contains, startsWith, endsWith, indexOf, lastIndexOf, charAt |
| Transformación | toUpperCase, toLowerCase, trim, strip, stripLeading, stripTrailing, replace, replaceAll, replaceFirst, repeat |
| Extracción | substring, split, chars |
| Construcción | String.join, String.format, .formatted, +, repeat |
| Comparación | equals, equalsIgnoreCase, compareTo, compareToIgnoreCase |
| Inmutabilidad | Ningún método modifica el original; todos devuelven nuevos String |
| Multilínea | Text blocks """...""" (Java 15+) |
13. Cuestionario de autoevaluación
Preguntas
-
¿Qué significa que
Stringsea inmutable? Pon un ejemplo de código que lo demuestre. -
¿Cuál es la diferencia entre
==y.equals()al comparar dos strings? -
¿Qué es el string pool y por qué
new String("Maria")suele ser una mala idea? -
¿Cuál es la diferencia entre
trim()ystrip()? ¿Cuál deberías usar en código nuevo? -
¿Qué devuelve
"Hola, mundo".substring(6)? ¿Y"Hola, mundo".substring(0, 4)? -
¿Por qué
"192.168.1.1".split(".")no devuelve["192", "168", "1", "1"]? ¿Cómo se soluciona? -
¿Qué devuelve
"Maria".compareTo("Ana")? ¿Y"Ana".compareTo("Maria")? ¿Y"Maria".compareTo("Maria")? -
¿Qué método usarías para comprobar si un string está compuesto solo por espacios? ¿Por qué
isEmpty()no sería suficiente? -
Escribe una línea que devuelva
"Hola Maria"a partir de" hola maria "(sin espacios, con la H y la M en mayúsculas). -
¿Cuál es la diferencia entre
isEmpty()eisBlank()? Pon un ejemplo con la cadena" ". -
¿Qué imprime el siguiente código?
-
¿Cómo se escriben los especificadores de formato para: (a) un string, (b) un entero, (c) un decimal con 2 decimales, (d) un salto de línea portable?
-
¿Qué método permite repetir un string N veces? ¿Desde qué versión de Java está disponible?
-
¿Qué diferencia hay entre
replace(char, char)yreplaceAll(String, String)? -
Escribe una sola línea de código que una los strings
"Maria","Carlos"y"Ana"con el separador", "usando un método estático deString.
Soluciones
-
Significa que, una vez creado, el contenido de un
Stringno puede cambiar. Cualquier método que parezca modificarlo devuelve unStringnuevo. Ejemplo:Para que surta efecto hay que asignar:s = s.toUpperCase();. -
==compara referencias (direcciones de memoria);.equals()compara contenidos.==puede dartrueaccidentalmente con literales del pool, pero falla con strings creados connewo leídos de fichero. -
El string pool es una zona del heap donde Java guarda los literales de string y los reutiliza.
new String("Maria")crea un objeto nuevo fuera del pool, desperdiciando memoria, porque ya existe"Maria"en el pool. Basta con usar el literal"Maria". -
trim()elimina solo espacios ASCII con código <= U+0020;strip()(Java 11+) usa la definición Unicode completa. En código nuevo, usa siemprestrip(). -
"Hola, mundo".substring(6)devuelve"mundo"(desde el índice 6 hasta el final)."Hola, mundo".substring(0, 4)devuelve"Hola"(índice 0 incluido, índice 4 excluido: 4 caracteres). -
Porque el separador que recibe
splites una expresión regular, y en regex el punto.significa "cualquier carácter". Solución: escapar consplit("\\."). -
"Maria".compareTo("Ana")devuelve un número positivo (porque "Maria" va después de "Ana")."Ana".compareTo("Maria")devuelve un número negativo."Maria".compareTo("Maria")devuelve0. -
isBlank().isEmpty()devuelvefalsepara" "porque tiene 3 caracteres, pero para nosotros suele ser un valor "vacío" en términos de validación. -
" hola maria ".strip().replace("hola", "Hola").replace("maria", "Maria")o, más conciso," hola maria ".strip()seguido de operaciones de capitalización (la capitalización correcta de la primera letra de cada palabra requiere lógica que verás en unidades avanzadas; para esta pregunta basta conreplace). -
isEmpty()devuelvefalsepara" "(tiene 3 caracteres).isBlank()devuelvetrue(solo contiene espacios). -
Imprime
Java. La llamada atoUpperCase()devuelve un nuevoStringque se descarta porque no se asigna a ninguna variable. -
(a)
%s, (b)%d, (c)%.2f, (d)%n. -
repeat(int), disponible desde Java 11. -
replace(char, char)yreplace(CharSequence, CharSequence)reemplazan literalmente (sin interpretar regex).replaceAll(String, String)interpreta el primer argumento como una expresión regular. Para reemplazos literales usareplace; para patrones usareplaceAll. -
String.join(", ", "Maria", "Carlos", "Ana").
Evaluación del cuestionario
| Aciertos | Nivel |
|---|---|
| 14-15 | Dominas la sección. Adelante con la siguiente. |
| 10-13 | Bien, repasa los puntos fallados. |
| 6-9 | Repasa la sección, especialmente inmutabilidad e índices. |
| Menos de 6 | Vuelve a leer con calma y ejecuta el ejemplo integrador línea por línea en IntelliJ. |
14. Información ampliada y próximos pasos
- Javadoc oficial de
String(Java 25): https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/String.html - Artículo "String (Java Platform SE) — Inmutabilidad y pool" (Baeldung): https://www.baeldung.com/java-string-pool
- Tutorial de Oracle sobre texto y formateo: https://docs.oracle.com/javase/tutorial/java/data/index.html
- JEP 378: Text Blocks (multilínea): https://openjdk.org/jeps/378
Próximos pasos:
- Practica en IntelliJ. Copia el ejemplo integrador, ejecútalo, modifícalo: prueba a cambiar el email por uno sin arroba, por uno solo con espacios, por uno con mayúsculas y minúsculas mezcladas. Observa el comportamiento.
- Lee el Javadoc de
String. Recorre la lista de métodos públicos y prueba los que no hayamos visto en clase (por ejemplo,indent,transform,chars). Es un ejercicio excelente de exploración del API. - Avanza a la Sección 4 de UD2. La clase
Math: constantes, funciones matemáticas,Math.clamp(Java 21+) y la introducción a la generación de aleatorios conMath.random(). Es una sección más corta y ligera, ideal para asentar lo aprendido aquí antes de entrar enRandomyjava.timeen las secciones 5 y 6.