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} €.*"));truematches 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());}450,00 €87,35 €23,10 €Pattern.compile(...)convierte el patrón en un objetoPattern. Hazlo una vez y úsalo tantas veces como quieras.amount.matcher(text)da unMatcher: un buscador de este patrón en este texto.m.find()encuentra la siguiente coincidencia. Devuelvetruesi la hay, yfalsecuando se acaban. Por eso está en unwhile.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,replaceAllyspliten realidad llaman aPattern.compilecada 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 campostatic final Pattern.m.matches()comprueba todo el texto, comoString.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);ERROR de 0 a 5ERROR de 26 a 31Total: 2start() 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();\d+ es «uno o más dígitos», así que «12» es una sola coincidencia. Con \d sin +, cada dígito iría por separado: [1][2][4][2]. El espacio y la coma cortan la secuencia, así que 12 y 4 no se juntan.
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));}15.11.202615 / 11 / 20262026-11-15- Los grupos se numeran por sus paréntesis de apertura, de izquierda a derecha, empezando por 1.
group(0)ogroup()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
- El patrón de la fecha tiene tres pares de paréntesis: tres grupos.
- m es un buscador de este patrón en este texto.
- find() encuentra la fecha: del índice 7 al 17.
- Cada grupo recuerda su parte: el grupo 1 es 15, el grupo 2 es 11, el grupo 3 es 2026.
- Las partes están en m, cada una bajo su número. group(0) es toda la coincidencia.
- group() es lo mismo que group(0): 15.11.2026.
- Los tres grupos, uno por uno.
- 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)esnull.
La idea
Grupos con nombre y reemplazo
(?<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 + " €");}Chocolate caliente: 6.4 €Tostada: 2.9 €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}"));Plazo: 2026-11-15, recordatorio: 2026-11-08Plazo: 11/15/2026, recordatorio: 11/08/2026Pattern 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 porMatcher.quoteReplacement(...)si viene del usuario.
- También puedes usar un grupo en el propio patrón:
\1significa «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());}Una sola coincidencia, toda la línea. .+ toma todo lo que puede: llega al final del texto y luego devuelve carácter a carácter hasta encontrar un >. El primer > desde el final es el último de la línea. Los cuantificadores así se llaman codiciosos.
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("<[^>]+>", ""));<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.
if llama a find() una sola vez y encuentra solo la primera coincidencia. Para todas necesitas while (m.find()): cada llamada sigue desde donde se detuvo la anterior.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));}Sin conexión con la base de datosDisco 95%Pattern.CASE_INSENSITIVEes lo mismo que(?i): mayúsculas y minúsculas son iguales.Pattern.MULTILINEhace 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.DOTALLpermite 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));Repetida: caféRepetida: favorfalse 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.
Este es contenido viejo. Más contenido viejo, no los viejos.Este es contenido nuevo. Más contenido nuevo, no los viejos.Reemplazos: 22. Palabras con prefijo. La primera línea es el prefijo, la segunda es el texto. Imprime cada palabra que empiece con el prefijo.
proprograma producto banana aprobar profesor improvisarprogramaproductoprofesor3. 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.
Mira el sitio https://www.example.com y este: http://www.test.com. Los documentos están en https://docs.example.com/guide/start.https://www.example.comhttp://www.test.comhttps://docs.example.com/guide/start4. Fechas a ISO. Convierte cada fecha dd.mm.aaaa del texto a la forma aaaa-mm-dd con un solo replaceAll.
Contrato del 03.10.2026, pago hasta el 15.10.2026.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 yreplaceAll.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · static: lo que comparten todos los objetos · Cadenas · Expresiones regulares
- Ejercicios sobre el tema: Texto y archivos