Ir al contenido
wannacode

Expresiones regulares: grupos y reemplazo

En resumen: Los grupos de una expresión regular, partes entre paréntesis, extraen trozos de cada coincidencia; en Java los encuentras con Pattern y Matcher y los usas en un reemplazo con $1.

Problema

El extracto del banco

Tu banco te envía un texto con los pagos del mes. Quieres cada importe por separado para sumarlos:

String statement = "Alquiler 450,00 € el 01.10, luz 87,35 € el 05.10, agua 23,10 € el 12.10";
System.out.println(statement.matches(".*\\d+,\\d{2} €.*"));
Salida
true
matches solo dice «sí, hay un importe en algún sitio». El .* a ambos lados está ahí porque matches quiere que el patrón cubra todo el texto. replaceAll cambiaría los importes, pero no te los da. Necesitas una forma de encontrar cada coincidencia y tomarla.

La idea

Pattern y Matcher

Para buscar hay dos clases de java.util.regex:

String statement = "Alquiler 450,00 € el 01.10, luz 87,35 € el 05.10, agua 23,10 € el 12.10";
Pattern amount = Pattern.compile("\\d+,\\d{2} €");
Matcher m = amount.matcher(statement);
while (m.find()) {
System.out.println(m.group());
}
Salida
450,00 €
87,35 €
23,10 €
  • Pattern.compile(...) convierte el patrón en un objeto Pattern. Hazlo una vez y úsalo tantas veces como quieras.
  • amount.matcher(text) da un Matcher: un buscador de este patrón en este texto.
  • m.find() encuentra la siguiente coincidencia. Devuelve true si la hay, y false cuando se acaban. Por eso está en un while.
  • m.group() es el texto de la coincidencia que acaba de encontrar.

Al principio del archivo necesitas import java.util.regex.Matcher; e import java.util.regex.Pattern;.

Más precisamente, para curiosos
  • String.matches, replaceAll y split en realidad llaman a Pattern.compile cada vez. En un bucle con miles de líneas es más rápido compilar el patrón una sola vez, por ejemplo en un campo static final Pattern.
  • m.matches() comprueba todo el texto, como String.matches. m.lookingAt() comprueba solo si el texto empieza con una coincidencia.

Míralo

Dónde está la coincidencia

Además del texto, el Matcher sabe también dónde se encontró:

String log = "ERROR disco; INFO inicio; ERROR red";
Matcher m = Pattern.compile("ERROR").matcher(log);
int count = 0;
while (m.find()) {
count++;
System.out.println(m.group() + " de " + m.start() + " a " + m.end());
}
System.out.println("Total: " + count);
Salida
ERROR de 0 a 5
ERROR de 26 a 31
Total: 2
start() es el índice del primer carácter de la coincidencia, y end() es el índice después del último, como con substring. Por eso log.substring(m.start(), m.end()) es lo mismo que m.group().

Pruébalo

¿Un dígito o un número?

¿Qué imprimirá el programa?

Matcher m = Pattern.compile("\\d+").matcher("Mesa 12, 4 personas, 2 botellas");
while (m.find()) {
System.out.print("[" + m.group() + "]");
}
System.out.println();
Respuesta

La idea

Grupos: partes de la coincidencia

Los paréntesis en el patrón forman un grupo. El Matcher recuerda por separado lo que coincidió con cada grupo:

Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");
Matcher m = date.matcher("Vence: 15.11.2026");
if (m.find()) {
System.out.println(m.group());
System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3));
System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));
}
Salida
15.11.2026
15 / 11 / 2026
2026-11-15
  • Los grupos se numeran por sus paréntesis de apertura, de izquierda a derecha, empezando por 1.
  • group(0) o group() es toda la coincidencia.
  • if (m.find()) sirve cuando solo necesitas la primera coincidencia.

Así tomas por separado el día, el mes y el año de una fecha, y los ordenas como necesites.

Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");
Matcher m = date.matcher("Vence: 15.11.2026");
if (m.find()) {
System.out.println(m.group());
System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3));
System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));
}
Paso a paso, en palabras
  1. El patrón de la fecha tiene tres pares de paréntesis: tres grupos.
  2. m es un buscador de este patrón en este texto.
  3. find() encuentra la fecha: del índice 7 al 17.
  4. Cada grupo recuerda su parte: el grupo 1 es 15, el grupo 2 es 11, el grupo 3 es 2026.
  5. Las partes están en m, cada una bajo su número. group(0) es toda la coincidencia.
  6. group() es lo mismo que group(0): 15.11.2026.
  7. Los tres grupos, uno por uno.
  8. En orden inverso, las partes dan 2026-11-15.
Más precisamente, para curiosos
  • (?:...) es un grupo que solo mantiene las partes juntas, sin recordarlas. Por ejemplo, (?:\+359|0)8[789]\d{7} no forma un grupo 1.
  • m.groupCount() da el número de grupos del patrón.
  • Si un grupo no participó en la coincidencia (por ejemplo, (,\d{2})? no estaba), m.group(1) es null.

La idea

Grupos con nombre y reemplazo

Cuando hay muchos grupos, los números se confunden. Puedes ponerles nombre: (?<nombre>...), y luego m.group("nombre"). Aquí tienes una línea de la cuenta de una cafetería:
Pattern item = Pattern.compile("(?<qty>\\d+) x (?<name>[A-Za-z ]+) a (?<price>\\d+\\.\\d{2})");
Matcher m = item.matcher("2 x Chocolate caliente a 3.20; 1 x Tostada a 2.90");
while (m.find()) {
int qty = Integer.parseInt(m.group("qty"));
double price = Double.parseDouble(m.group("price"));
System.out.println(m.group("name") + ": " + qty * price + " €");
}
Salida
Chocolate caliente: 6.4 €
Tostada: 2.9 €
Los grupos también funcionan en replaceAll: en el texto de reemplazo, $1 es el grupo 1, y ${nombre} es el grupo con ese nombre. Aquí van las fechas, de la forma día.mes.año a ISO y a la forma estadounidense:
String text = "Plazo: 15.11.2026, recordatorio: 08.11.2026";
System.out.println(text.replaceAll("(\\d{2})\\.(\\d{2})\\.(\\d{4})", "$3-$2-$1"));
System.out.println(text.replaceAll("(?<d>\\d{2})\\.(?<m>\\d{2})\\.(?<y>\\d{4})", "${m}/${d}/${y}"));
Salida
Plazo: 2026-11-15, recordatorio: 2026-11-08
Plazo: 11/15/2026, recordatorio: 11/08/2026
Aquí no necesitas Pattern ni Matcher: String.replaceAll entiende los grupos por sí solo.
Más precisamente, para curiosos
  • $ en el texto de reemplazo es especial. Si quieres un signo de dólar de verdad, escribe \\$, o pasa el texto por Matcher.quoteReplacement(...) si viene del usuario.
  • También puedes usar un grupo en el propio patrón: \1 significa «lo mismo que el grupo 1». Así se atrapan palabras repetidas. Mira la tarjeta de las banderas.

Pruébalo

¿Cuánto toma +?

Quieres todas las etiquetas HTML de la línea:

¿Qué imprimirá el programa?

String html = "<b>Precio:</b> <i>12 €</i>";
Matcher m = Pattern.compile("<.+>").matcher(html);
while (m.find()) {
System.out.println(m.group());
}
Respuesta

La idea

Codicioso y perezoso

Un ? después de un cuantificador lo hace perezoso: toma lo menos que puede y se detiene en la primera oportunidad.

String html = "<b>Precio:</b> <i>12 €</i>";
Matcher m = Pattern.compile("<.+?>").matcher(html);
while (m.find()) {
System.out.println(m.group());
}
System.out.println(html.replaceAll("<[^>]+>", ""));
Salida
<b>
</b>
<i>
</i>
Precio: 12 €
  • <.+?> es «<, al menos un carácter y el primer > que venga después».
  • <[^>]+> hace lo mismo de otra manera: «<, caracteres que no son >, y >». No puede saltarse un >, así que además es más rápido. Con él quitas las etiquetas y solo queda el texto.
Codicioso Perezoso
* *?
+ +?
? ??
{2,5} {2,5}?

Pruébalo

Encuentra el error

El programa debería imprimir los tres números de pedido, pero imprime solo #1042. ¿En qué línea está el error?

Elige la línea con el error.

La idea

Banderas y líneas

Las banderas se pasan como segundo argumento de Pattern.compile, unidas con |. De un archivo de registro con varias líneas tomas los mensajes de error, escritos en mayúsculas o en minúsculas:

String log = "INFO: Inicio\nERROR: Sin conexión con la base de datos\nINFO: Reintentando\nerror: Disco 95%";
Pattern p = Pattern.compile("^error: (.+)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(log);
while (m.find()) {
System.out.println(m.group(1));
}
Salida
Sin conexión con la base de datos
Disco 95%
  • Pattern.CASE_INSENSITIVE es lo mismo que (?i): mayúsculas y minúsculas son iguales.
  • Pattern.MULTILINE hace que ^ y $ sean el inicio y el final de cada línea. Sin ella, son solo el inicio y el final de todo el texto, y el patrón no encontrará nada.
  • Pattern.DOTALL permite que . atrape también un salto de línea.
Más precisamente, para curiosos

Dos cosas que encontrarás en patrones ajenos:

  • Una referencia hacia atrás (backreference) \1: «el mismo texto que el grupo 1». Así se atrapan palabras repetidas.
  • Una búsqueda hacia delante (lookahead) (?=...): «de aquí en adelante hay en algún sitio ...», sin tomar caracteres. Varias seguidas son útiles para contraseñas: «tiene un dígito» y «tiene una mayúscula», y al menos 8 caracteres.
Pattern twice = Pattern.compile("(?U)\\b(\\w+) \\1\\b");
Matcher m = twice.matcher("Te espero en el café café, por favor favor sé puntual.");
while (m.find()) {
System.out.println("Repetida: " + m.group(1));
}
String strong = "(?=.*\\d)(?=.*[A-Z]).{8,}";
System.out.println("secreto12".matches(strong) + " " + "Secreto12".matches(strong));
Salida
Repetida: café
Repetida: favor
false true

(?!...) es lo contrario: «de aquí en adelante no hay ...». También existe la búsqueda hacia atrás (lookbehind): (?<=...) y (?<!...).

Ejercicio

Encontrar, extraer, reemplazar

Resuelve las tareas en tu editor. El texto es una línea del teclado. En la lección «Archivos» las harás también con un archivo.

1. Buscar y reemplazar. Reemplaza la palabra «viejo» por «nuevo» en todos los sitios donde sea una palabra completa, y di cuántos reemplazos has hecho. Las partes de otras palabras («viejos») no se tocan.

Entrada
Este es contenido viejo. Más contenido viejo, no los viejos.
Salida
Este es contenido nuevo. Más contenido nuevo, no los viejos.
Reemplazos: 2

2. Palabras con prefijo. La primera línea es el prefijo, la segunda es el texto. Imprime cada palabra que empiece con el prefijo.

Entrada
pro
programa producto banana aprobar profesor improvisar
Salida
programa
producto
profesor

3. Direcciones en un texto. Extrae todas las direcciones web que empiezan con http:// o https://. El punto al final de la frase no forma parte de la dirección.

Entrada
Mira el sitio https://www.example.com y este: http://www.test.com. Los documentos están en https://docs.example.com/guide/start.
Salida
https://www.example.com
http://www.test.com
https://docs.example.com/guide/start

4. Fechas a ISO. Convierte cada fecha dd.mm.aaaa del texto a la forma aaaa-mm-dd con un solo replaceAll.

Entrada
Contrato del 03.10.2026, pago hasta el 15.10.2026.
Salida
Contrato del 2026-10-03, pago hasta el 2026-10-15.

Más tareas

  • Total del extracto. Toma los importes de la primera tarjeta con un grupo para los euros y otro para los céntimos, súmalos e imprime «Total: 560,45 €».
  • Hashtags. Extrae todas las palabras que empiezan con # de una publicación que mezcla letras latinas sin tilde y letras como «é» o «ñ». Piensa por qué necesitas (?U).
  • Sin etiquetas. Quita las etiquetas HTML de <p>El plazo es el <b>15.11</b>.</p> e imprime el texto limpio.
  • Teléfonos en un texto. Encuentra todos los números de móvil de un texto e imprímelos en la forma +359 88 812 3456, con grupos y replaceAll.

Test

Ponte a prueba

1. ¿Qué hace m.find()?
2. El patrón es (\d{2})\.(\d{2})\.(\d{4}) y el texto es 15.11.2026. ¿Qué es group(2)?
3. ¿Qué da "05.03.2026".replaceAll("(\\d+)\\.(\\d+)\\.(\\d+)", "$2/$1")?
4. ¿Por qué "<.+>" atrapa todo el texto "<b>Precio</b>" y no solo "<b>"?
5. ¿Qué hace Pattern.MULTILINE?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·