Ir al contenido principal

Expresiones regulares en Java

Introducción

Las expresiones regulares (o regex) son patrones de búsqueda que permiten realizar operaciones avanzadas de búsqueda, extracción y manipulación de texto. En Java, esta funcionalidad está implementada en el paquete java.util.regex, que proporciona herramientas potentes para trabajar con patrones de texto. Las expresiones regulares son especialmente útiles cuando necesitamos validar formatos (como correos electrónicos o números de teléfono), extraer información específica de un texto o realizar sustituciones complejas basadas en patrones.

En este artículo, aprenderemos qué son las expresiones regulares, cómo utilizarlas en Java, y exploraremos las clases principales que nos permiten aprovechar su potencia. También veremos ejemplos prácticos de las tareas más comunes que se pueden realizar con este poderoso recurso del lenguaje.

Conceptos básicos de expresiones regulares

¿Qué es una expresión regular?

Una expresión regular es una secuencia de caracteres que forma un patrón de búsqueda. Este patrón puede ser desde muy simple (como buscar una palabra específica) hasta extremadamente complejo (como validar una dirección de correo electrónico con requisitos específicos).

Clases principales en Java para trabajar con expresiones regulares

En Java, las expresiones regulares se implementan principalmente a través de dos clases:

  • Pattern: Representa un patrón de expresión regular compilado.
  • Matcher: Utiliza un patrón para realizar operaciones de coincidencia en un texto.

Veamos cómo utilizar estas clases con un ejemplo simple:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EjemploRegex {
    public static void main(String[] args) {
        // El texto donde buscaremos
        String texto = "Mi número de teléfono es 612-345-678 y mi código postal es 28001";
        
        // Definir el patrón para buscar números de teléfono
        String patron = "\\d{3}-\\d{3}-\\d{3}";
        
        // Compilar el patrón
        Pattern pattern = Pattern.compile(patron);
        
        // Crear un objeto Matcher
        Matcher matcher = pattern.matcher(texto);
        
        // Buscar coincidencias
        if (matcher.find()) {
            System.out.println("Teléfono encontrado: " + matcher.group());
        }
    }
}

En este ejemplo:

  1. Definimos un patrón que busca una secuencia de tres dígitos, guion, tres dígitos, guion, y tres dígitos más.
  2. Compilamos el patrón con Pattern.compile().
  3. Creamos un Matcher asociando el patrón con el texto donde buscar.
  4. Utilizamos matcher.find() para buscar coincidencias y matcher.group() para obtener el texto coincidente.

Sintaxis básica de expresiones regulares

Caracteres literales

Los caracteres normales como letras y números se representan a sí mismos. Por ejemplo, el patrón casa coincide exactamente con la palabra "casa".

Metacaracteres

Los metacaracteres tienen significados especiales en las expresiones regulares:

Metacarácter Descripción
. Cualquier carácter excepto salto de línea
^ Inicio de línea
$ Fin de línea
\d Un dígito (equivalente a [0-9])
\D Un carácter que no es dígito
\s Un espacio en blanco (espacio, tabulación, salto de línea)
\S Un carácter que no es espacio en blanco
\w Un carácter alfanumérico o guion bajo (equivalente a [a-zA-Z0-9_])
\W Un carácter que no es alfanumérico ni guion bajo

Cuantificadores

Los cuantificadores especifican cuántas veces puede aparecer un elemento:

Cuantificador Descripción
* Cero o más veces
+ Una o más veces
? Cero o una vez
{n} Exactamente n veces
{n,} Al menos n veces
{n,m} Entre n y m veces

Clases de caracteres

Las clases de caracteres permiten especificar un conjunto de caracteres posibles:

Expresión Descripción
[abc] Cualquiera de los caracteres a, b o c
[^abc] Cualquier carácter excepto a, b o c
[a-z] Cualquier carácter entre a y z
[a-zA-Z0-9] Cualquier letra o dígito

Veamos un ejemplo utilizando algunos de estos elementos:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EjemploSintaxisRegex {
    public static void main(String[] args) {
        String texto = "El precio es 23,50€ y el descuento es del 15%";
        
        // Buscar números decimales (con coma)
        String patron = "\\d+,\\d+";
        Pattern pattern = Pattern.compile(patron);
        Matcher matcher = pattern.matcher(texto);
        
        System.out.println("Números decimales encontrados:");
        while (matcher.find()) {
            System.out.println(matcher.group());
        }
        
        // Buscar porcentajes (número seguido de %)
        patron = "\\d+%";
        pattern = Pattern.compile(patron);
        matcher = pattern.matcher(texto);
        
        System.out.println("\nPorcentajes encontrados:");
        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
}

Este programa encuentra "23,50" como número decimal y "15%" como porcentaje.

Operaciones comunes con expresiones regulares

Verificar si un texto coincide completamente con un patrón

Para verificar si un texto coincide completamente con un patrón, usamos matches():

String email = "usuario@dominio.com";
boolean esValido = email.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
System.out.println("¿El email es válido? " + esValido);

Buscar todas las coincidencias

Para encontrar todas las coincidencias de un patrón en un texto:

String texto = "Contactos: juan@example.com, maria@ejemplo.es, pedro@sitio.org";
String patronEmail = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
Pattern pattern = Pattern.compile(patronEmail);
Matcher matcher = pattern.matcher(texto);

System.out.println("Correos electrónicos encontrados:");
while (matcher.find()) {
    System.out.println(matcher.group());
}

Reemplazar texto basado en un patrón

Para sustituir texto basándose en un patrón:

String texto = "Mi teléfono es 612-345-678";
String resultado = texto.replaceAll("\\d{3}-\\d{3}-\\d{3}", "XXX-XXX-XXX");
System.out.println(resultado); // Imprime: Mi teléfono es XXX-XXX-XXX

Dividir un texto utilizando un patrón como separador

Para dividir un texto utilizando un patrón como separador:

String texto = "manzana,naranja;plátano:uva";
String[] frutas = texto.split("[,;:]"); // Divide por coma, punto y coma o dos puntos
for (String fruta : frutas) {
    System.out.println(fruta);
}

Grupos de captura

Los grupos de captura permiten extraer partes específicas de las coincidencias:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EjemploGruposCaptura {
    public static void main(String[] args) {
        String texto = "Fecha: 23/04/2023";
        String patron = "Fecha: (\\d{2})/(\\d{2})/(\\d{4})";
        
        Pattern pattern = Pattern.compile(patron);
        Matcher matcher = pattern.matcher(texto);
        
        if (matcher.find()) {
            String dia = matcher.group(1);
            String mes = matcher.group(2);
            String anio = matcher.group(3);
            
            System.out.println("Día: " + dia);
            System.out.println("Mes: " + mes);
            System.out.println("Año: " + anio);
        }
    }
}

En este ejemplo:

  1. Los paréntesis () crean grupos de captura.
  2. Cada grupo puede ser accedido después con matcher.group(número).
  3. El grupo 0 siempre es la coincidencia completa.

Validación de formatos comunes

Validación de correo electrónico

public static boolean validarEmail(String email) {
    String patron = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
    return Pattern.matches(patron, email);
}

Validación de número de teléfono español

public static boolean validarTelefonoEspanol(String telefono) {
    // Acepta formatos: 612345678, 612-345-678, +34612345678, etc.
    String patron = "^(\\+34|0034|34)?[6789]\\d{8}$";
    return Pattern.matches(patron, telefono);
}

Validación de código postal español

public static boolean validarCodigoPostal(String cp) {
    String patron = "^\\d{5}$";  // 5 dígitos
    if (!Pattern.matches(patron, cp)) {
        return false;
    }
    
    // Los dos primeros dígitos deben estar entre 01 y 52
    int provincia = Integer.parseInt(cp.substring(0, 2));
    return provincia >= 1 && provincia <= 52;
}

Consideraciones de rendimiento

Las expresiones regulares son potentes pero pueden afectar al rendimiento si no se utilizan correctamente:

  1. Reutiliza objetos Pattern: La compilación de patrones es costosa, así que es mejor compilar un patrón una vez y reutilizarlo.

    // Mejor rendimiento (patrón compilado una sola vez)
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
        
    public static boolean validarEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
    
  2. Usa patrones eficientes: Algunas construcciones (como la búsqueda hacia atrás o lookahead/lookbehind) pueden ser costosas.

  3. Evita expresiones regulares para tareas simples: Si solo necesitas verificar si un texto contiene una subcadena, String.contains() es más eficiente.

Ejemplo práctico completo: Extractor de información

A continuación, un ejemplo más completo que extrae información de un texto estructurado:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExtractorInformacion {
    public static void main(String[] args) {
        String texto = "Cliente: Juan Pérez\n" +
                      "NIF: 12345678A\n" +
                      "Email: juan.perez@example.com\n" +
                      "Teléfono: 612-345-678\n" +
                      "Fecha alta: 15/03/2023";
        
        // Patrones para extraer información
        extraerInformacion(texto, "Cliente: (.+)", "Nombre del cliente");
        extraerInformacion(texto, "NIF: ([0-9]{8}[A-Z])", "NIF");
        extraerInformacion(texto, "Email: ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})", "Email");
        extraerInformacion(texto, "Teléfono: (\\d{3}-\\d{3}-\\d{3})", "Teléfono");
        extraerInformacion(texto, "Fecha alta: (\\d{2}/\\d{2}/\\d{4})", "Fecha de alta");
    }
    
    private static void extraerInformacion(String texto, String patron, String descripcion) {
        Pattern pattern = Pattern.compile(patron);
        Matcher matcher = pattern.matcher(texto);
        
        if (matcher.find()) {
            System.out.println(descripcion + ": " + matcher.group(1));
        } else {
            System.out.println(descripcion + ": No encontrado");
        }
    }
}

Este programa extrae y muestra información específica (nombre, NIF, email, teléfono y fecha) de un texto estructurado utilizando diferentes patrones de expresión regular.

Resumen

Las expresiones regulares son una herramienta fundamental para el procesamiento de texto en Java. A través del paquete java.util.regex y sus clases principales (Pattern y Matcher), podemos realizar desde operaciones básicas como validación de formatos hasta tareas complejas como extracción y manipulación de datos textuales.

En este artículo hemos explorado la sintaxis básica de las expresiones regulares, las operaciones más comunes, y hemos visto ejemplos prácticos de validación y extracción de información. Aunque pueden parecer complejas al principio, con práctica se convierten en una herramienta indispensable para cualquier programador Java, especialmente cuando se trabaja con datos de texto estructurados o se necesita validar entradas de usuario.

En el próximo artículo, aprenderemos sobre entrada/salida avanzada con archivos y streams, lo que complementará perfectamente lo aprendido sobre expresiones regulares para trabajar con datos externos.