Módulo 09: Cadenas de caracteres
Objetivos del módulo
- Entender que
Stringes un objeto inmutable y qué es el String pool - Crear y concatenar cadenas conociendo las trampas del operador
+ - Dominar los métodos principales de
String - Comparar cadenas con
equals,equalsIgnoreCaseycompareTo, y saber por qué==no sirve - Recorrer una cadena carácter a carácter y usar la clase
Character - Convertir entre cadenas y números, y tratar
NumberFormatException - Construir cadenas eficientemente con
StringBuilder - Escribir textos de varias líneas con text blocks (Java 15) y formatear con
String.formatyformatted - Validar datos con expresiones regulares básicas (DNI, email, código postal)
- Resolver problemas clásicos con cadenas: palíndromos, anagramas, cifrado César…
1. String: un objeto inmutable
Desde el primer «Hola, mundo» has usado cadenas, pero String es distinto de int o double: no es un tipo primitivo, sino una clase. Cada cadena es un objeto, y una variable String guarda una referencia a ese objeto, igual que las variables de tipo array del módulo 08.
📘 Concepto: Un
Stringes una secuencia inmutable de caracteres: una vez creado, no se puede modificar. Todos los métodos que parecen cambiarlo (toUpperCase,replace,trim,substring…) en realidad crean y devuelven unStringnuevo y dejan el original intacto.
public class Inmutable {
public static void main(String[] args) {
String s = "Hola";
s.toUpperCase(); // crea "HOLA"... y se pierde
System.out.println(s); // s no ha cambiado
String t = s.toUpperCase(); // hay que guardar el resultado
System.out.println(t);
s = s + " mundo"; // crea un String NUEVO y s pasa a apuntarlo
System.out.println(s);
}
}
Hola
HOLA
Hola mundo
La segunda línea de main es un error muy frecuente: llamar a s.toUpperCase() sin guardar el resultado no sirve de nada.
Figura 1. Concatenar no modifica el objeto original: crea uno nuevo.
¿Por qué Java hace inmutables los String? Porque así se pueden compartir sin peligro (nadie puede cambiar el texto que otro está usando), son seguros entre hilos de ejecución y pueden guardarse en un almacén común, el String pool.
1.1. El String pool
Los literales de texto (las cadenas escritas entre comillas en el código) se guardan en una zona especial del montón llamada String pool. Si dos literales tienen el mismo texto, Java los guarda una sola vez y las dos variables apuntan al mismo objeto. En cambio, new String("hola") fuerza la creación de un objeto nuevo, y las cadenas que se construyen al ejecutar (leídas del teclado, concatenadas con variables…) también son objetos nuevos.
Figura 2. Dos literales iguales comparten objeto en el pool; new String crea otro distinto.
public class StringPool {
public static void main(String[] args) {
String a = "hola"; // literal: va al String pool
String b = "hola"; // reutiliza el MISMO objeto del pool
String c = new String("hola"); // fuerza un objeto nuevo fuera del pool
String d = "ho" + "la"; // constantes: se une al compilar -> pool
String parte = "ho";
String e = parte + "la"; // se une al ejecutar -> objeto nuevo
System.out.println("a == b: " + (a == b));
System.out.println("a == c: " + (a == c));
System.out.println("a == d: " + (a == d));
System.out.println("a == e: " + (a == e));
System.out.println("a.equals(c): " + a.equals(c));
System.out.println("a.equals(e): " + a.equals(e));
System.out.println("a == e.intern(): " + (a == e.intern()));
}
}
a == b: true
a == c: false
a == d: true
a == e: false
a.equals(c): true
a.equals(e): true
a == e.intern(): true
"ho" + "la"son dos constantes y el compilador las une al compilar: el resultado es el literal"hola", que está en el pool.parte + "la"se calcula al ejecutar y produce un objeto nuevo.intern()devuelve la versión del pool de una cadena (la añade si no estaba). Se usa muy poco; lo importante es entender el concepto.
💡 Consejo: No escribas nunca
new String("texto"): crea un objeto innecesario. Escribe simplemente"texto".
2. Crear y concatenar cadenas
El operador + concatena cadenas. Si uno de los operandos es un String, el otro se convierte automáticamente a texto. Pero ojo, porque + se evalúa de izquierda a derecha:
public class Concatenar {
public static void main(String[] args) {
String nombre = "Ada";
int edad = 36;
String frase = "Me llamo " + nombre + " y tengo " + edad + " años";
System.out.println(frase);
// + se evalúa de izquierda a derecha
System.out.println(1 + 2 + "3"); // (1 + 2) + "3" -> "33"
System.out.println("1" + 2 + 3); // ("1" + 2) + 3 -> "123"
System.out.println("1" + (2 + 3)); // paréntesis -> "15"
System.out.println('a' + 'b' + "!"); // char + char es int: 97 + 98
String s = "Java";
s += " 25"; // s = s + " 25"
s = s.concat(" LTS");
System.out.println(s);
System.out.println("null: " + null); // concatenar null no falla
}
}
Me llamo Ada y tengo 36 años
33
123
15
195!
Java 25 LTS
null: null
| Expresión | Resultado | Por qué |
|---|---|---|
1 + 2 + "3" | "33" | Primero 1 + 2 = 3 (números); luego 3 + "3" |
"1" + 2 + 3 | "123" | "1" + 2 = "12"; luego "12" + 3 |
"1" + (2 + 3) | "15" | Los paréntesis fuerzan la suma primero |
'a' + 'b' + "!" | "195!" | char + char es una suma de enteros: 97 + 98 |
⚠️ Importante: Un
Stringpuede valernull(no apunta a ningún objeto). Concatenarnullproduce el texto"null", pero llamar a un método sobre él (s.length()cons == null) lanzaNullPointerException. Distingue bien entrenull(no hay cadena) y""(cadena vacía, de longitud 0).
3. Métodos principales de String
Cada carácter de una cadena tiene un índice, empezando en 0, como en los arrays:
Figura 3. Índices de una cadena y resultado de algunos métodos.
import java.util.Arrays;
public class MetodosString {
public static void main(String[] args) {
String s = "Programar en Java";
System.out.println("length(): " + s.length());
System.out.println("charAt(0): " + s.charAt(0));
System.out.println("charAt(13): " + s.charAt(13));
System.out.println("substring(13): " + s.substring(13));
System.out.println("substring(0, 9): " + s.substring(0, 9));
System.out.println("indexOf('a'): " + s.indexOf('a'));
System.out.println("indexOf(\"en\"): " + s.indexOf("en"));
System.out.println("indexOf('a', 6): " + s.indexOf('a', 6));
System.out.println("lastIndexOf('a'): " + s.lastIndexOf('a'));
System.out.println("indexOf('z'): " + s.indexOf('z'));
System.out.println("contains(\"Java\"): " + s.contains("Java"));
System.out.println("startsWith(\"Pro\"): " + s.startsWith("Pro"));
System.out.println("endsWith(\"va\"): " + s.endsWith("va"));
System.out.println("toUpperCase(): " + s.toUpperCase());
System.out.println("toLowerCase(): " + s.toLowerCase());
System.out.println("replace('a', '4'): " + s.replace('a', '4'));
System.out.println("replace(\"Java\", \"C\"): " + s.replace("Java", "C"));
String conEspacios = " hola ";
System.out.println("trim(): [" + conEspacios.trim() + "]");
System.out.println("strip(): [" + conEspacios.strip() + "]");
System.out.println("stripLeading(): [" + conEspacios.stripLeading() + "]");
System.out.println("isEmpty() de \" \": " + " ".isEmpty());
System.out.println("isBlank() de \" \": " + " ".isBlank());
String csv = "rojo,verde,azul";
String[] colores = csv.split(",");
System.out.println("split(\",\"): " + Arrays.toString(colores));
System.out.println("join(\" | \"): " + String.join(" | ", colores));
System.out.println("repeat(3): " + "ab".repeat(3));
System.out.println("chars() de 'a': " + s.chars().filter(c -> c == 'a').count());
}
}
length(): 17
charAt(0): P
charAt(13): J
substring(13): Java
substring(0, 9): Programar
indexOf('a'): 5
indexOf("en"): 10
indexOf('a', 6): 7
lastIndexOf('a'): 16
indexOf('z'): -1
contains("Java"): true
startsWith("Pro"): true
endsWith("va"): true
toUpperCase(): PROGRAMAR EN JAVA
toLowerCase(): programar en java
replace('a', '4'): Progr4m4r en J4v4
replace("Java", "C"): Programar en C
trim(): [hola]
strip(): [hola]
stripLeading(): [hola ]
isEmpty() de " ": false
isBlank() de " ": true
split(","): [rojo, verde, azul]
join(" | "): rojo | verde | azul
repeat(3): ababab
chars() de 'a': 4
| Método | Devuelve | Ejemplo con s = "Programar en Java" |
|---|---|---|
length() | Número de caracteres | 17 |
charAt(i) | El carácter de la posición i (char) | charAt(13) → 'J' |
substring(i) | Desde i hasta el final | substring(13) → "Java" |
substring(i, j) | Desde i (incluido) hasta j (excluido) | substring(0, 9) → "Programar" |
indexOf(x) | Primera posición de x (char o String), o -1 | indexOf("en") → 10 |
indexOf(x, desde) | Igual, empezando a buscar en desde | indexOf('a', 6) → 7 |
lastIndexOf(x) | Última posición de x, o -1 | lastIndexOf('a') → 16 |
contains(t) | ¿Contiene el texto t? | contains("Java") → true |
startsWith(t) / endsWith(t) | ¿Empieza / termina por t? | startsWith("Pro") → true |
toUpperCase() / toLowerCase() | Copia en mayúsculas / minúsculas | "PROGRAMAR EN JAVA" |
trim() | Copia sin espacios al principio ni al final (solo ASCII) | " hola " → "hola" |
strip() (Java 11) | Como trim, pero reconoce todos los espacios Unicode | También stripLeading() y stripTrailing() |
isEmpty() | ¿Longitud 0? | " ".isEmpty() → false |
isBlank() (Java 11) | ¿Vacía o solo espacios? | " ".isBlank() → true |
replace(a, b) | Copia con todas las apariciones de a cambiadas por b | replace('a', '4') |
split(regex) | Array de trozos separados por el patrón | "a,b".split(",") → [a, b] |
String.join(sep, partes) | Une las partes con el separador | join(" \| ", colores) |
repeat(n) (Java 11) | La cadena repetida n veces | "ab".repeat(3) → "ababab" |
chars() | Los caracteres como flujo de enteros (stream) | s.chars().filter(c -> c == 'a').count() → 4 |
toCharArray() | Array char[] con los caracteres | Útil para recorrer o ordenar |
💡 Consejo: La regla «el inicio se incluye y el final se excluye» de
substring(i, j)tiene una ventaja: la longitud del resultado esj - i. Esta misma convención la usanArrays.copyOfRange,Random.nextInt(a, b)y muchos otros métodos de Java.
📘 Concepto:
chars()devuelve un stream, yc -> c == 'a'es una expresión lambda. Son herramientas de programación funcional que estudiarás en el Curso de Java Avanzado; aquí solo las mencionamos para que las reconozcas.
4. Comparar cadenas
Ya lo adelantamos en el módulo 05: nunca compares el contenido de dos String con ==. Ahora que conoces el pool, entiendes el motivo: == compara referencias, y dos cadenas con el mismo texto pueden ser objetos distintos (o el mismo, si ambas vienen del pool, lo que hace que == «funcione» a veces y engañe).
| Método | Pregunta | Resultado |
|---|---|---|
a.equals(b) | ¿Mismo texto? | boolean |
a.equalsIgnoreCase(b) | ¿Mismo texto sin distinguir mayúsculas? | boolean |
a.compareTo(b) | ¿Qué cadena va antes en orden lexicográfico? | int: negativo, 0 o positivo |
a.compareToIgnoreCase(b) | Igual, sin distinguir mayúsculas | int |
a == b | ¿Es el mismo objeto? | boolean (casi nunca es lo que quieres) |
public class Comparar {
public static void main(String[] args) {
String a = "casa", b = "Casa", c = "cosa";
System.out.println("equals: " + a.equals(b));
System.out.println("equalsIgnoreCase: " + a.equalsIgnoreCase(b));
// compareTo: negativo si a va antes, 0 si son iguales, positivo si va después
System.out.println("casa vs cosa: " + a.compareTo(c)); // 'a' - 'o'
System.out.println("cosa vs casa: " + c.compareTo(a));
System.out.println("casa vs casa: " + a.compareTo("casa"));
System.out.println("casa vs Casa: " + a.compareTo(b)); // 'c' - 'C'
System.out.println("ignorando mayúsc.: " + a.compareToIgnoreCase(b));
System.out.println("casa vs casas: " + a.compareTo("casas")); // más corta, antes
if (a.compareTo(c) < 0) {
System.out.println(a + " va antes que " + c);
}
}
}
equals: false
equalsIgnoreCase: true
casa vs cosa: -14
cosa vs casa: 14
casa vs casa: 0
casa vs Casa: 32
ignorando mayúsc.: 0
casa vs casas: -1
casa va antes que cosa
compareTo compara carácter a carácter hasta encontrar una diferencia y devuelve la resta de sus códigos ('a' - 'o' = 97 - 111 = -14). Si una cadena es el principio de la otra, devuelve la diferencia de longitudes. Lo único que debes mirar es el signo: a.compareTo(b) < 0 significa «a va antes que b».
⚠️ Importante:
compareTousa los códigos Unicode, así que las mayúsculas van antes que las minúsculas ("Z" < "a") y las letras con tilde y la ñ van después de laz. Para ordenar palabras en español correctamente existe la clasejava.text.Collator, que verás en el curso avanzado.
5. Recorrer los caracteres y la clase Character
Hay dos formas de recorrer una cadena: con un for y charAt(i), o con un for-each sobre toCharArray(). Para clasificar cada carácter se usan los métodos static de la clase Character (módulo 07):
| Método | Pregunta / acción |
|---|---|
Character.isLetter(c) | ¿Es una letra? (incluye ñ y letras acentuadas) |
Character.isDigit(c) | ¿Es un dígito? |
Character.isLetterOrDigit(c) | ¿Letra o dígito? |
Character.isUpperCase(c) / isLowerCase(c) | ¿Mayúscula / minúscula? |
Character.isWhitespace(c) | ¿Espacio, tabulador o salto de línea? |
Character.toUpperCase(c) / toLowerCase(c) | Devuelve el carácter convertido |
import java.util.Scanner;
public class Caracteres {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Escribe una frase: ");
String frase = sc.nextLine();
int letras = 0, mayusculas = 0, digitos = 0, espacios = 0, otros = 0;
for (int i = 0; i < frase.length(); i++) {
char c = frase.charAt(i);
if (Character.isLetter(c)) {
letras++;
if (Character.isUpperCase(c)) {
mayusculas++;
}
} else if (Character.isDigit(c)) {
digitos++;
} else if (Character.isWhitespace(c)) {
espacios++;
} else {
otros++;
}
}
System.out.println("Letras: " + letras + " (mayúsculas: " + mayusculas + ")");
System.out.println("Dígitos: " + digitos + ", espacios: " + espacios + ", otros: " + otros);
// Recorrido con for-each sobre el array de caracteres
StringBuilder alterno = new StringBuilder();
boolean mayus = true;
for (char c : frase.toCharArray()) {
if (Character.isLetter(c)) {
alterno.append(mayus ? Character.toUpperCase(c) : Character.toLowerCase(c));
mayus = !mayus;
} else {
alterno.append(c);
}
}
System.out.println("Alternando: " + alterno);
// Aritmética con char: un char es un número (su código Unicode)
char letra = 'C';
System.out.println("Posición de 'C' en el alfabeto: " + (letra - 'A' + 1));
System.out.println("Valor del dígito '7': " + ('7' - '0'));
System.out.println("Siguiente letra: " + (char) (letra + 1));
}
}
Escribe una frase: ¡Hola Mundo! Java 25 en 2026.
Letras: 15 (mayúsculas: 3)
Dígitos: 6, espacios: 5, otros: 3
Alternando: ¡HoLa MuNdO! jAvA 25 eN 2026.
Posición de 'C' en el alfabeto: 3
Valor del dígito '7': 7
Siguiente letra: D
Un char es en realidad un número (su código Unicode), así que se puede operar con él:
letra - 'A' + 1da la posición de una mayúscula en el alfabeto inglés ('C'→ 3).'7' - '0'convierte un carácter dígito en su valor (7), porque los dígitos tienen códigos consecutivos.(char) (letra + 1)da la letra siguiente: el cast es necesario porqueletra + 1es unint.
6. Conversión entre String y números
Lo que el usuario escribe, lo que se lee de un fichero o lo que llega por la red es siempre texto. Para operar con ello hay que convertirlo:
| Conversión | Método | Ejemplo |
|---|---|---|
String → int | Integer.parseInt(s) | Integer.parseInt("1234") → 1234 |
String → double | Double.parseDouble(s) | Double.parseDouble("3.75") → 3.75 |
String → long / boolean | Long.parseLong(s) / Boolean.parseBoolean(s) | |
String en otra base → int | Integer.parseInt(s, base) | Integer.parseInt("ff", 16) → 255 |
número → String | String.valueOf(x) o Integer.toString(x) | String.valueOf(3.5) → "3.5" |
int → binario / hexadecimal | Integer.toBinaryString(x) / Integer.toHexString(x) | "1010", "ff" |
char → String / String → char | String.valueOf(c) / s.charAt(0) |
public class Conversiones {
public static void main(String[] args) {
// De String a número
int n = Integer.parseInt("1234");
double d = Double.parseDouble("3.75");
long grande = Long.parseLong("9000000000");
boolean b = Boolean.parseBoolean("TRUE");
System.out.println(n + 1); // ahora sí suma: 1235
System.out.println(d * 2);
System.out.println(grande + " " + b);
System.out.println(Integer.parseInt(" 42 ".strip())); // quitar espacios antes
System.out.println(Integer.parseInt("ff", 16)); // en base 16
// De número a String
String s1 = String.valueOf(3.5);
String s2 = Integer.toString(255);
String s3 = "" + 99; // funciona, pero es menos claro
System.out.println(s1 + s2 + s3); // concatenación de textos
System.out.println(Integer.toBinaryString(10) + " " + Integer.toHexString(255));
// char <-> String
String deChar = String.valueOf('x');
char deString = "xyz".charAt(0);
System.out.println(deChar + deString);
}
}
1235
7.5
9000000000 true
42
255
3.525599
1010 ff
xx
📘 Concepto:
Integer,Double,Long,Boolean… son las clases envoltorio (wrapper classes) de los tipos primitivos. Además de estos métodos de conversión ofrecen constantes comoInteger.MAX_VALUE. Las estudiarás en el módulo 11.
6.1. NumberFormatException
Si el texto no representa un número válido, parseInt lanza una excepción:
public class NumeroInvalido {
public static void main(String[] args) {
String texto = "12a";
int n = Integer.parseInt(texto);
System.out.println(n);
}
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:565)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at NumeroInvalido.main(NumeroInvalido.java:4)
En el módulo 15 aprenderás a capturar esta excepción con try/catch. Mientras tanto, puedes comprobar el texto antes de convertirlo, por ejemplo con una expresión regular (sección 9). Es una técnica muy útil para leer números sin que el programa se detenga por un error de tecleo: se lee la línea completa como texto y solo se convierte si es correcta.
import java.util.Scanner;
public class LeerConParse {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int edad = -1;
while (edad < 0) {
System.out.print("Edad: ");
String linea = sc.nextLine().strip();
if (linea.matches("\\d+")) { // solo dígitos (sección 9)
edad = Integer.parseInt(linea);
} else {
System.out.println("'" + linea + "' no es un número entero válido.");
}
}
System.out.println("Dentro de 10 años tendrás " + (edad + 10));
}
}
Edad: veinte
'veinte' no es un número entero válido.
Edad: 20 años
'20 años' no es un número entero válido.
Edad: 20
Dentro de 10 años tendrás 30
⚠️ Importante:
Double.parseDoubleusa siempre el punto como separador decimal, sin importar la configuración regional del sistema:Double.parseDouble("3,75")lanzaNumberFormatException.Scanner.nextDouble(), en cambio, sí depende de la configuración regional (módulo 04).
7. StringBuilder: cadenas modificables
Como los String son inmutables, construir un texto a base de += dentro de un bucle crea un objeto nuevo en cada vuelta y copia todo el texto anterior. Para eso existe StringBuilder: un objeto que representa una cadena modificable, con un búfer interno que crece según hace falta.
Figura 4. Concatenar en un bucle: muchos objetos con String, uno solo con StringBuilder.
| Método | Acción |
|---|---|
new StringBuilder() / new StringBuilder("texto") | Crea uno vacío o con un texto inicial |
append(x) | Añade al final (cualquier tipo). Devuelve el propio StringBuilder, así que se puede encadenar |
insert(i, x) | Inserta en la posición i |
delete(i, j) / deleteCharAt(i) | Borra el tramo [i, j) / el carácter i |
replace(i, j, texto) | Sustituye el tramo [i, j) |
setCharAt(i, c) | Cambia un carácter |
reverse() | Invierte el contenido |
length(), charAt(i), indexOf(t) | Como en String |
toString() | Devuelve el String resultante |
public class Constructor {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Java");
sb.append(" es").append(" genial"); // encadenar llamadas
System.out.println(sb);
sb.insert(0, "¡"); // insertar en una posición
sb.append('!');
System.out.println(sb + " (longitud " + sb.length() + ")");
sb.setCharAt(1, 'j'); // cambiar un carácter
System.out.println(sb);
sb.deleteCharAt(0); // borrar un carácter
sb.delete(4, 7); // borrar de 4 a 6
System.out.println(sb);
sb.replace(0, 4, "Kotlin"); // sustituir un tramo
System.out.println(sb);
System.out.println(sb.reverse()); // invertir (modifica sb)
String resultado = sb.toString(); // convertir a String
System.out.println(resultado.toUpperCase());
}
}
Java es genial
¡Java es genial! (longitud 16)
¡java es genial!
java genial!
Kotlin genial!
!laineg niltoK
!LAINEG NILTOK
7.1. ¿Cuánto se nota?
public class Rendimiento {
public static void main(String[] args) {
final int N = 20_000;
long inicio = System.nanoTime();
String s = "";
for (int i = 0; i < N; i++) {
s += "x"; // crea un String nuevo en cada vuelta
}
long msString = (System.nanoTime() - inicio) / 1_000_000;
inicio = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
sb.append("x"); // modifica el mismo objeto
}
String t = sb.toString();
long msBuilder = (System.nanoTime() - inicio) / 1_000_000;
System.out.println("Con String y +=: " + msString + " ms (" + s.length() + " caracteres)");
System.out.println("Con StringBuilder: " + msBuilder + " ms (" + t.length() + " caracteres)");
}
}
Una ejecución real (los tiempos dependen del ordenador, pero la proporción se mantiene):
Con String y +=: 511 ms (20000 caracteres)
Con StringBuilder: 11 ms (20000 caracteres)
Con += el tiempo crece con el cuadrado del número de vueltas (en la vuelta k hay que copiar k caracteres): O(n²). Con StringBuilder crece de forma lineal: O(n). Con el doble de vueltas, la versión con String tardaría unas cuatro veces más.
💡 Consejo: Usa
+para concatenaciones sueltas ("Hola, " + nombre): es más legible y el compilador lo optimiza. UsaStringBuildercuando construyas un texto dentro de un bucle. (Existe tambiénStringBuffer, una versión antigua y más lenta pensada para varios hilos; en código nuevo usaStringBuilder.)
8. Text blocks y formato
8.1. Text blocks (Java 15)
Un text block es un literal de varias líneas delimitado por tres comillas """. Es ideal para JSON, HTML, SQL o menús, porque no hay que escribir \n ni escapar las comillas:
public class TextBlocks {
public static void main(String[] args) {
// Antes de Java 15
String antiguo = "{\n" +
" \"nombre\": \"Ada\",\n" +
" \"lenguaje\": \"Java\"\n" +
"}";
// Con un text block
String json = """
{
"nombre": "Ada",
"lenguaje": "Java"
}""";
System.out.println(json);
System.out.println("¿Iguales? " + antiguo.equals(json));
// La sangría común se elimina; el cierre """ marca dónde empieza el texto
String menu = """
MENÚ
1. Nuevo
2. Abrir
""";
System.out.print(menu);
// \ al final de línea: no inserta salto; formatted() rellena huecos
String carta = """
Estimado/a %s: \
su pedido %d sale hoy.
""".formatted("Ada", 1024);
System.out.print(carta);
}
}
{
"nombre": "Ada",
"lenguaje": "Java"
}
¿Iguales? true
MENÚ
1. Nuevo
2. Abrir
Estimado/a Ada: su pedido 1024 sale hoy.
Reglas:
- Tras las
"""de apertura debe haber un salto de línea; el texto empieza en la línea siguiente. - Java elimina la sangría común de todas las líneas. La posición de las
"""de cierre también cuenta: si las pones en su propia línea, el texto termina con un salto de línea (como enmenu); si las pones al final de la última línea, no (como enjson). \al final de una línea evita el salto de línea;\srepresenta un espacio que no se elimina.
8.2. String.format y formatted
String.format funciona exactamente igual que printf (módulo 04), pero en lugar de escribir el resultado lo devuelve como String. Desde Java 15, formatted hace lo mismo llamado sobre la propia plantilla:
public class Formatear {
public static void main(String[] args) {
String nombre = "Ada";
double saldo = 12345.678;
int puntos = 1500000;
String s1 = String.format("Cliente: %s, saldo: %.2f €", nombre, saldo);
String s2 = "Cliente: %s, puntos: %,d".formatted(nombre, puntos); // Java 15
System.out.println(s1);
System.out.println(s2);
// Tablas alineadas: ancho fijo, - alinea a la izquierda
System.out.println(String.format("|%-10s|%8s|", "Producto", "Precio"));
System.out.println(String.format("|%-10s|%8.2f|", "Café", 1.5));
System.out.println(String.format("|%-10s|%8.2f|", "Bocadillo", 4.25));
System.out.println(String.format("Código: %05d", 42)); // relleno con ceros
System.out.println(String.format("%s en mayúsculas: %S", nombre, nombre));
}
}
Cliente: Ada, saldo: 12345.68 €
Cliente: Ada, puntos: 1,500,000
|Producto | Precio|
|Café | 1.50|
|Bocadillo | 4.25|
Código: 00042
Ada en mayúsculas: ADA
| Especificador | Significado |
|---|---|
%s / %S | Texto / texto en mayúsculas |
%d, %5d, %05d, %,d | Entero; con ancho 5; relleno con ceros; con separador de miles |
%.2f, %8.2f | Decimal con 2 decimales; con ancho 8 |
%-10s | Texto alineado a la izquierda en 10 posiciones |
%c, %b, %n, %% | Carácter, booleano, salto de línea, símbolo % |
9. Expresiones regulares básicas
Una expresión regular (regex) es un patrón que describe un conjunto de cadenas: «ocho dígitos seguidos de una letra», «uno o más espacios», «algo, una arroba, algo, un punto y al menos dos letras». Se usan para validar, buscar y reemplazar texto.
| Elemento | Significado | Ejemplo | Encaja con |
|---|---|---|---|
abc | Esos caracteres literales | hola | hola |
[abc] | Uno de esos caracteres | [aeiou] | Una vocal |
[a-z], [A-Z0-9] | Un carácter del rango | [0-9] | Un dígito |
[^abc] | Cualquier carácter excepto esos | [^0-9] | Un no dígito |
. | Cualquier carácter | a.c | abc, a7c |
\d / \D | Un dígito / un no dígito | \d\d | 42 |
\w | Letra (sin tildes), dígito o _ | \w+ | user_1 |
\s / \S | Un espacio en blanco / un no espacio | a\sb | a b |
X? | X opcional (0 o 1 vez) | colou?r | color, colour |
X* | 0 o más veces | ab* | a, abbb |
X+ | 1 o más veces | \d+ | 7, 2026 |
X{n}, X{n,m}, X{n,} | Exactamente n veces; entre n y m; al menos n | \d{8} | 12345678 |
(X\|Y) | X o Y (grupo con alternativas) | (0[1-9]\|1[0-2]) | Un mes de 01 a 12 |
⚠️ Importante: En Java, la barra invertida
\también es el carácter de escape de los literales de texto. Por eso en el código hay que duplicarla: el patrón\d{8}se escribe"\\d{8}"y\s+se escribe"\\s+".
Métodos de String que aceptan expresiones regulares:
| Método | Uso |
|---|---|
s.matches(regex) | ¿El texto completo encaja con el patrón? |
s.replaceAll(regex, reemplazo) | Sustituye todos los fragmentos que encajan |
s.split(regex) | Divide por los fragmentos que encajan |
import java.util.Arrays;
public class Regex {
public static void main(String[] args) {
// matches: ¿el texto COMPLETO encaja con el patrón?
String[] dnis = {"12345678Z", "1234567Z", "12345678z", "X1234567L"};
for (String dni : dnis) {
System.out.println(dni + " -> formato DNI: " + dni.matches("\\d{8}[A-Z]"));
}
String patronEmail = "[\\w.+-]+@[\\w-]+(\\.[\\w-]+)*\\.[a-zA-Z]{2,}";
String[] emails = {"ada@ejemplo.com", "luis.perez@correo.edu.es", "mal@dominio", "@falta.com"};
for (String e : emails) {
System.out.println(e + " -> email: " + e.matches(patronEmail));
}
// Código postal español: provincias 01 a 52 + 3 dígitos
String patronCP = "(0[1-9]|[1-4]\\d|5[0-2])\\d{3}";
String[] cps = {"03001", "28080", "53000", "7080"};
for (String cp : cps) {
System.out.println(cp + " -> código postal: " + cp.matches(patronCP));
}
// replaceAll y split con expresiones regulares
String sucio = " Esto tiene demasiados espacios ";
System.out.println("[" + sucio.strip().replaceAll("\\s+", " ") + "]");
System.out.println("[" + "Tel: 965-12-34-56".replaceAll("\\D", "") + "]");
System.out.println(Arrays.toString("uno, dos;tres ,cuatro".split("\\s*[,;]\\s*")));
}
}
12345678Z -> formato DNI: true
1234567Z -> formato DNI: false
12345678z -> formato DNI: false
X1234567L -> formato DNI: false
ada@ejemplo.com -> email: true
luis.perez@correo.edu.es -> email: true
mal@dominio -> email: false
@falta.com -> email: false
03001 -> código postal: true
28080 -> código postal: true
53000 -> código postal: false
7080 -> código postal: false
[Esto tiene demasiados espacios]
[965123456]
[uno, dos, tres, cuatro]
Cómo leer los patrones del ejemplo:
- DNI:
\d{8}[A-Z]→ ocho dígitos y una letra mayúscula. Solo valida el formato; la letra debe comprobarse además con un cálculo (ejercicio 3). - Email:
[\w.+-]+@[\w-]+(\.[\w-]+)*\.[a-zA-Z]{2,}→ uno o más caracteres permitidos,@, un dominio, opcionalmente más subdominios y un punto con al menos dos letras. Validar un email de forma exhaustiva es muy complicado; este patrón sencillo detecta los errores típicos. - Código postal:
(0[1-9]|[1-4]\d|5[0-2])\d{3}→ los dos primeros dígitos son la provincia (de 01 a 52) y luego tres dígitos más. replaceAll("\\s+", " ")sustituye cualquier grupo de espacios por uno solo, yreplaceAll("\\D", "")borra todo lo que no sea un dígito.split("\\s*[,;]\\s*")divide por comas o puntos y comas, con o sin espacios alrededor.
💡 Consejo: Para probar expresiones regulares mientras las escribes, IntelliJ IDEA permite comprobarlas directamente: sitúa el cursor sobre el patrón, pulsa
Alt+Introy elige Check RegExp. Para patrones complejos, Java ofrece las clasesPatternyMatcherdel paquetejava.util.regex.
10. Ejemplos completos
10.1. Palíndromos
Un palíndromo se lee igual de izquierda a derecha que de derecha a izquierda, sin tener en cuenta espacios, signos, mayúsculas ni tildes. Primero se normaliza el texto y después se compara con su inverso:
import java.util.Scanner;
public class Palindromo {
static String normalizar(String s) {
StringBuilder sb = new StringBuilder();
for (char c : s.toLowerCase().toCharArray()) {
switch (c) {
case 'á' -> c = 'a';
case 'é' -> c = 'e';
case 'í' -> c = 'i';
case 'ó' -> c = 'o';
case 'ú', 'ü' -> c = 'u';
default -> { }
}
if (Character.isLetterOrDigit(c)) { // descarta espacios y signos
sb.append(c);
}
}
return sb.toString();
}
static boolean esPalindromo(String s) {
String limpio = normalizar(s);
String alReves = new StringBuilder(limpio).reverse().toString();
return limpio.equals(alReves);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Frase: ");
String frase = sc.nextLine();
System.out.println("Normalizada: " + normalizar(frase));
System.out.println(esPalindromo(frase) ? "Es un palíndromo" : "No es un palíndromo");
}
}
Frase: Dábale arroz a la zorra el abad
Normalizada: dabalearrozalazorraelabad
Es un palíndromo
new StringBuilder(limpio).reverse().toString() es la forma habitual de invertir un String. El switch con flechas sobre la variable c sustituye las vocales acentuadas.
10.2. Contar vocales
import java.util.Scanner;
public class ContarVocales {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Texto: ");
String texto = sc.nextLine().toLowerCase();
int a = 0, e = 0, i = 0, o = 0, u = 0;
for (int k = 0; k < texto.length(); k++) {
switch (texto.charAt(k)) {
case 'a', 'á' -> a++;
case 'e', 'é' -> e++;
case 'i', 'í' -> i++;
case 'o', 'ó' -> o++;
case 'u', 'ú', 'ü' -> u++;
default -> { } // el resto de caracteres no cuenta
}
}
System.out.printf("a: %d e: %d i: %d o: %d u: %d -> total: %d%n",
a, e, i, o, u, a + e + i + o + u);
}
}
Texto: El murciélago es un animal único y curioso
a: 3 e: 3 i: 4 o: 4 u: 4 -> total: 18
10.3. Invertir palabras
public class InvertirPalabras {
public static void main(String[] args) {
String frase = "Java es un lenguaje divertido";
// 1) Invertir el orden de las palabras
String[] palabras = frase.split(" ");
StringBuilder orden = new StringBuilder();
for (int i = palabras.length - 1; i >= 0; i--) {
orden.append(palabras[i]);
if (i > 0) {
orden.append(' ');
}
}
System.out.println(orden);
// 2) Invertir las letras de cada palabra, manteniendo el orden
String[] invertidas = new String[palabras.length];
for (int i = 0; i < palabras.length; i++) {
invertidas[i] = new StringBuilder(palabras[i]).reverse().toString();
}
System.out.println(String.join(" ", invertidas));
// 3) Invertir la frase entera
System.out.println(new StringBuilder(frase).reverse());
}
}
divertido lenguaje un es Java
avaJ se nu ejaugnel oditrevid
oditrevid ejaugnel nu se avaJ
10.4. Cifrado César
Julio César cifraba sus mensajes desplazando cada letra un número fijo de posiciones en el alfabeto: con desplazamiento 3, la A pasa a ser D, la B pasa a ser E… y la Z vuelve a empezar por la C. La aritmética de char y el operador % lo resuelven:
public class Cesar {
static String cifrar(String texto, int desplazamiento) {
int k = ((desplazamiento % 26) + 26) % 26; // admite negativos
StringBuilder sb = new StringBuilder();
for (char c : texto.toCharArray()) {
if (c >= 'A' && c <= 'Z') {
sb.append((char) ('A' + (c - 'A' + k) % 26));
} else if (c >= 'a' && c <= 'z') {
sb.append((char) ('a' + (c - 'a' + k) % 26));
} else {
sb.append(c); // espacios, signos, ñ...
}
}
return sb.toString();
}
static String descifrar(String texto, int desplazamiento) {
return cifrar(texto, -desplazamiento);
}
public static void main(String[] args) {
String mensaje = "Ataque al amanecer, Zona Norte";
String secreto = cifrar(mensaje, 3);
System.out.println("Original: " + mensaje);
System.out.println("Cifrado: " + secreto);
System.out.println("Descifrado: " + descifrar(secreto, 3));
}
}
Original: Ataque al amanecer, Zona Norte
Cifrado: Dwdtxh do dpdqhfhu, Crqd Qruwh
Descifrado: Ataque al amanecer, Zona Norte
c - 'A'convierte la letra en su posición (0 a 25), se suma el desplazamiento,% 26hace que dé la vuelta y'A' + ...la convierte de nuevo en letra.((desplazamiento % 26) + 26) % 26normaliza el desplazamiento a un valor entre 0 y 25, incluso si es negativo. Así, descifrar es cifrar con el desplazamiento opuesto.- Este cifrado solo trata las 26 letras del alfabeto inglés; la ñ y las vocales acentuadas se dejan como están.
10.5. Validador de contraseñas
import java.util.Scanner;
public class Password {
static String problemas(String p) {
boolean mayus = false, minus = false, digito = false, especial = false;
for (char c : p.toCharArray()) {
if (Character.isUpperCase(c)) mayus = true;
else if (Character.isLowerCase(c)) minus = true;
else if (Character.isDigit(c)) digito = true;
else if (!Character.isWhitespace(c)) especial = true;
}
StringBuilder sb = new StringBuilder();
if (p.length() < 8) sb.append("\n - Debe tener al menos 8 caracteres");
if (!mayus) sb.append("\n - Falta una mayúscula");
if (!minus) sb.append("\n - Falta una minúscula");
if (!digito) sb.append("\n - Falta un dígito");
if (!especial) sb.append("\n - Falta un carácter especial (como ! o #)");
if (p.contains(" ")) sb.append("\n - No puede contener espacios");
return sb.toString();
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String errores;
do {
System.out.print("Nueva contraseña: ");
errores = problemas(sc.nextLine());
if (!errores.isEmpty()) {
System.out.println("Contraseña débil:" + errores);
}
} while (!errores.isEmpty());
System.out.println("Contraseña aceptada.");
}
}
Nueva contraseña: hola
Contraseña débil:
- Debe tener al menos 8 caracteres
- Falta una mayúscula
- Falta un dígito
- Falta un carácter especial (como ! o #)
Nueva contraseña: holamundo2026
Contraseña débil:
- Falta una mayúscula
- Falta un carácter especial (como ! o #)
Nueva contraseña: Hola Mundo 2026!
Contraseña débil:
- No puede contener espacios
Nueva contraseña: HolaMundo2026!
Contraseña aceptada.
El método problemas devuelve un texto con todos los fallos (vacío si no hay ninguno), así el usuario sabe qué corregir. Los if sin llaves de una línea son una excepción aceptable cuando todos tienen la misma forma y caben en una línea, pero no abuses.
10.6. Anagramas
Dos palabras son anagramas si tienen exactamente las mismas letras en distinto orden. Truco: si se ordenan las letras de las dos, deben quedar iguales.
import java.util.Arrays;
public class Anagramas {
static boolean sonAnagramas(String a, String b) {
char[] x = a.replace(" ", "").toLowerCase().toCharArray();
char[] y = b.replace(" ", "").toLowerCase().toCharArray();
Arrays.sort(x); // mismas letras -> mismos arrays ordenados
Arrays.sort(y);
return Arrays.equals(x, y);
}
public static void main(String[] args) {
System.out.println("Roma / amor: " + sonAnagramas("Roma", "amor"));
System.out.println("Listen / Silent: " + sonAnagramas("Listen", "Silent"));
System.out.println("Delira / Lidera: " + sonAnagramas("Delira", "Lidera"));
System.out.println("Java / Ajo: " + sonAnagramas("Java", "Ajo"));
}
}
Roma / amor: true
Listen / Silent: true
Delira / Lidera: true
Java / Ajo: false
11. Ejercicios
Los seis primeros están resueltos; intenta hacerlos antes de mirar la solución.
Ejercicio 1: Contar palabras
Escribe un método que cuente las palabras de un texto, teniendo en cuenta que puede haber varios espacios seguidos y espacios al principio y al final.
Ver solución
import java.util.Scanner;
public class ContarPalabras {
static int contarPalabras(String texto) {
String limpio = texto.strip();
if (limpio.isEmpty()) {
return 0; // "".split(...) daría 1 elemento
}
return limpio.split("\\s+").length; // uno o más espacios como separador
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Texto: ");
String texto = sc.nextLine();
System.out.println("Palabras: " + contarPalabras(texto));
System.out.println("Texto vacío: " + contarPalabras(" "));
}
}
Texto: Hay que contar bien las palabras
Palabras: 6
Texto vacío: 0
split("\\s+") usa como separador uno o más espacios. strip() previo es necesario porque un espacio al principio produciría un primer elemento vacío. Y el caso del texto vacío hay que tratarlo aparte: "".split("\\s+") devuelve un array con un elemento (la cadena vacía), no un array vacío.
Ejercicio 2: Capitalizar
Escribe un método que ponga en mayúscula la primera letra de cada palabra y en minúscula el resto.
Ver solución
public class Capitalizar {
static String capitalizar(String frase) {
String[] palabras = frase.strip().toLowerCase().split("\\s+");
StringBuilder sb = new StringBuilder();
for (String p : palabras) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append(Character.toUpperCase(p.charAt(0))).append(p.substring(1));
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(capitalizar("el SEÑOR de los anillos"));
System.out.println(capitalizar("java"));
}
}
El Señor De Los Anillos
Java
Para cada palabra se toma charAt(0) en mayúscula y se le añade el resto (substring(1)), que ya está en minúsculas porque se convirtió todo el texto al principio. El if (sb.length() > 0) pone un espacio entre palabras, pero no antes de la primera.
Ejercicio 3: Letra del DNI
La letra del DNI español se obtiene dividiendo el número entre 23: el resto (0 a 22) indica la posición de la letra en la cadena "TRWAGMYFPDXBNJZSQVHLCKE". Escribe un método que calcule la letra y otro que valide un DNI completo (formato y letra).
Ver solución
public class LetraDni {
static final String LETRAS = "TRWAGMYFPDXBNJZSQVHLCKE";
static char letraDni(int numero) {
return LETRAS.charAt(numero % 23);
}
static boolean dniValido(String dni) {
dni = dni.strip().toUpperCase();
if (!dni.matches("\\d{8}[A-Z]")) {
return false;
}
int numero = Integer.parseInt(dni.substring(0, 8));
return dni.charAt(8) == letraDni(numero);
}
public static void main(String[] args) {
System.out.println("Letra de 12345678: " + letraDni(12345678));
String[] pruebas = {"12345678Z", "12345678A", "00000000t", "1234"};
for (String d : pruebas) {
System.out.println(d + " -> " + (dniValido(d) ? "válido" : "no válido"));
}
}
}
Letra de 12345678: Z
12345678Z -> válido
12345678A -> no válido
00000000t -> válido
1234 -> no válido
La cadena LETRAS funciona como una tabla: charAt(resto) sustituye a un switch de 23 casos. La validación combina las tres herramientas del módulo: una expresión regular para el formato, substring y parseInt para extraer el número, y la comparación de char con == (que sí es correcta, porque char es un tipo primitivo). Los NIE de extranjeros (que empiezan por X, Y o Z) siguen la misma regla sustituyendo esa letra por 0, 1 o 2.
Ejercicio 4: Compresión RLE
Escribe un método que comprima una cadena sustituyendo cada tramo de caracteres repetidos por el carácter y el número de repeticiones: "aaabccdddd" → "a3b1c2d4". Es una versión sencilla del algoritmo Run-Length Encoding.
Ver solución
public class Comprimir {
static String comprimir(String s) {
StringBuilder sb = new StringBuilder();
int i = 0;
while (i < s.length()) {
char c = s.charAt(i);
int repeticiones = 0;
while (i < s.length() && s.charAt(i) == c) { // contar el tramo
repeticiones++;
i++;
}
sb.append(c).append(repeticiones);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(comprimir("aaabccdddd"));
System.out.println(comprimir("xyz"));
System.out.println(comprimir("bbbbbbbbbbbb"));
}
}
a3b1c2d4
x1y1z1
b12
El bucle interno avanza i mientras el carácter se repita, así que el externo no necesita incrementarlo. Con un StringBuilder cada tramo se añade sin crear objetos intermedios. Observa que con "xyz" el resultado es más largo que el original: la compresión RLE solo compensa con muchas repeticiones.
Ejercicio 5: Ocurrencias de una subcadena
Escribe un método que cuente cuántas veces aparece una subcadena dentro de un texto usando indexOf.
Ver solución
public class Ocurrencias {
static int contar(String texto, String buscado) {
int veces = 0;
int pos = texto.indexOf(buscado);
while (pos != -1) {
veces++;
pos = texto.indexOf(buscado, pos + 1); // buscar a partir de la siguiente
}
return veces;
}
public static void main(String[] args) {
String texto = "el perro de san roque no tiene rabo porque ramón ramírez se lo ha robado";
System.out.println("'ra': " + contar(texto, "ra"));
System.out.println("'o': " + contar(texto, "o"));
System.out.println("'aa' en 'aaaa': " + contar("aaaa", "aa"));
}
}
'ra': 3
'o': 8
'aa' en 'aaaa': 3
indexOf(buscado, pos + 1) sigue buscando justo después de la última aparición encontrada. Así se cuentan también las apariciones solapadas: "aa" aparece 3 veces en "aaaa" (posiciones 0, 1 y 2). Si no quisieras contarlas, avanzarías con pos + buscado.length().
Ejercicio 6: Números romanos
Escribe un método que convierta un número romano en decimal. Recuerda que un símbolo se resta si va delante de otro de mayor valor (IV = 4, XC = 90, CM = 900).
Ver solución
public class Romanos {
static int valor(char c) {
return switch (c) {
case 'I' -> 1;
case 'V' -> 5;
case 'X' -> 10;
case 'L' -> 50;
case 'C' -> 100;
case 'D' -> 500;
case 'M' -> 1000;
default -> 0;
};
}
static int romanoADecimal(String romano) {
int total = 0;
for (int i = 0; i < romano.length(); i++) {
int actual = valor(romano.charAt(i));
int siguiente = (i + 1 < romano.length()) ? valor(romano.charAt(i + 1)) : 0;
if (actual < siguiente) {
total -= actual; // IV, IX, XL...: se resta
} else {
total += actual;
}
}
return total;
}
public static void main(String[] args) {
String[] pruebas = {"XIV", "MCMXCIX", "MMXXVI", "CDXLIV"};
for (String r : pruebas) {
System.out.println(r + " = " + romanoADecimal(r));
}
}
}
XIV = 14
MCMXCIX = 1999
MMXXVI = 2026
CDXLIV = 444
Se recorren los símbolos de izquierda a derecha: si el valor del símbolo actual es menor que el del siguiente, se resta; si no, se suma. En MCMXCIX: M (+1000), C (−100, va delante de M), M (+1000), X (−10), C (+100), I (−1), X (+10) = 1999. La expresión switch del método valor es la tabla de símbolos.
Ejercicio 7: Quitar las vocales (propuesto)
Escribe un método que devuelva una cadena sin vocales (con o sin tilde, en mayúscula o minúscula). Hazlo de dos formas: recorriendo los caracteres con un StringBuilder y con una única llamada a replaceAll.
Ejercicio 8: Pangramas (propuesto)
Un pangrama es una frase que contiene todas las letras del alfabeto. Escribe un método boolean esPangrama(String frase) para el alfabeto inglés (26 letras), usando un array de 26 boolean. Prueba con “The quick brown fox jumps over the lazy dog”.
Ejercicio 9: Frecuencia de letras (propuesto)
Lee un texto y muestra cuántas veces aparece cada letra (de la a a la z, sin distinguir mayúsculas), ordenadas de la más frecuente a la menos frecuente, ignorando las que no aparecen. Usa un array de 26 contadores indexado con c - 'a'.
Ejercicio 10: Matrículas (propuesto)
Las matrículas españolas actuales tienen cuatro dígitos y tres consonantes en mayúscula, sin vocales y sin las letras Ñ ni Q (por ejemplo, 1234 BCD). Escribe una expresión regular que las valide, admitiendo que el espacio intermedio sea opcional, y pruébala con casos válidos e inválidos.
Ejercicio 11: De decimal a romano (propuesto)
Escribe el método inverso del ejercicio 6: String decimalARomano(int n) para números entre 1 y 3999. Pista: usa dos arrays paralelos con los valores {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1} y sus símbolos, y un StringBuilder.
Ejercicio 12: Cifrado Vigenère (propuesto)
El cifrado Vigenère mejora el de César usando una palabra clave: cada letra del mensaje se desplaza según la letra correspondiente de la clave (A = 0, B = 1…), que se repite tantas veces como haga falta. Con la clave LIMON, el mensaje ATAQUE se cifra como LBMEHP. Escribe los métodos para cifrar y descifrar, reutilizando la idea del método cifrar de César.
Resumen
| Concepto | Sintaxis / idea clave |
|---|---|
| Inmutabilidad | Los métodos de String devuelven un objeto nuevo: s = s.toUpperCase(); |
| String pool | Los literales iguales comparten objeto; new String(...) crea otro |
| Concatenación | + de izquierda a derecha: 1 + 2 + "3" → "33" |
| Métodos básicos | length(), charAt(i), substring(i, j), indexOf, contains, replace, split, strip, repeat |
| Comparar | equals, equalsIgnoreCase, compareTo (mirar el signo); nunca == |
| Recorrer | for con charAt(i) o for-each sobre toCharArray() |
Character | isLetter, isDigit, isUpperCase, isWhitespace, toUpperCase |
| Conversiones | Integer.parseInt, Double.parseDouble, String.valueOf; texto inválido → NumberFormatException |
StringBuilder | append, insert, delete, reverse, toString; para construir texto en bucles |
| Text blocks (Java 15) | """ + salto de línea; se elimina la sangría común |
| Formato | String.format("%-10s %8.2f", ...) o "...".formatted(...) |
| Expresiones regulares | matches, replaceAll, split; \d, \w, \s, [...], +, *, ?, {n}; \\ en Java |