Expresiones regulares en Java, Parte 1: Coincidencia de patrones y la clase Patrón

Las clases de caracteres y cadenas variadas de Java ofrecen soporte de bajo nivel para la coincidencia de patrones, pero ese soporte generalmente conduce a un código complejo. Para una codificación más simple y eficiente, Java ofrece la API Regex. Este tutorial de dos partes lo ayuda a comenzar con las expresiones regulares y la API Regex. Primero descomprimiremos las tres clases poderosas que residen en el java.util.regexpaquete, luego exploraremos la Patternclase y sus construcciones sofisticadas de coincidencia de patrones.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

¿Qué son las expresiones regulares?

Una expresión regular , también conocida como regex o regexp , es una cadena cuyo patrón (plantilla) describe un conjunto de cadenas. El patrón determina qué cuerdas pertenecen al conjunto. Un patrón consta de caracteres literales y metacaracteres , que son caracteres que tienen un significado especial en lugar de un significado literal.

La coincidencia de patrones es el proceso de búsqueda de texto para identificar coincidencias o cadenas que coinciden con el patrón de una expresión regular. Java admite la coincidencia de patrones a través de su API Regex. La API consta de tres classes-- Pattern, Matchery PatternSyntaxException--all ubicado en el java.util.regexpaquete:

  • PatternLos objetos, también conocidos como patrones , son expresiones regulares compiladas.
  • MatcherLos objetos, o emparejadores , son motores que interpretan patrones para localizar coincidencias en secuencias de caracteres (objetos cuyas clases implementan la java.lang.CharSequenceinterfaz y sirven como fuentes de texto).
  • PatternSyntaxException los objetos describen patrones de expresiones regulares ilegales.

Java también proporciona soporte para la coincidencia de patrones a través de varios métodos en su java.lang.Stringclase. Por ejemplo, boolean matches(String regex)devuelve verdadero solo si la cadena de invocación coincide exactamente con regexla expresión regular.

Métodos de conveniencia

Detrás de escena, matches()y Stringotros métodos de conveniencia orientados a expresiones regulares se implementan en términos de la API de expresiones regulares.

RegexDemo

He creado la RegexDemoaplicación para demostrar expresiones regulares de Java y los diversos métodos que se encuentran en el Pattern, Matchery PatternSyntaxExceptionclases. Aquí está el código fuente de la demostración:

Listado 1. Demostración de expresiones regulares

import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class RegexDemo { public static void main(String[] args) { if (args.length != 2) { System.err.println("usage: java RegexDemo regex input"); return; } // Convert new-line (\n) character sequences to new-line characters. args[1] = args[1].replaceAll("\\\\n", "\n"); try { System.out.println("regex = " + args[0]); System.out.println("input = " + args[1]); Pattern p = Pattern.compile(args[0]); Matcher m = p.matcher(args[1]); while (m.find()) System.out.println("Found [" + m.group() + "] starting at " + m.start() + " and ending at " + (m.end() - 1)); } catch (PatternSyntaxException pse) { System.err.println("Bad regex: " + pse.getMessage()); System.err.println("Description: " + pse.getDescription()); System.err.println("Index: " + pse.getIndex()); System.err.println("Incorrect pattern: " + pse.getPattern()); } } }

La primera cosa RegexDemo's main()método no es para validar su línea de comandos. Esto requiere dos argumentos: el primer argumento es una expresión regular y el segundo argumento es el texto de entrada que debe coincidir con la expresión regular.

Es posible que desee especificar un carácter de nueva línea ( \n) como parte del texto de entrada. La única forma de lograr esto es especificar un \carácter seguido de un ncarácter. main()convierte esta secuencia de caracteres al valor Unicode 10.

La mayor parte del RegexDemocódigo de se encuentra en la construcción try- catch. El trybloque primero genera la expresión regular especificada y el texto de entrada y luego crea un Patternobjeto que almacena la expresión regular compilada. (Las expresiones regulares se compilan para mejorar el rendimiento durante la coincidencia de patrones). Se extrae un comparador del Patternobjeto y se usa para buscar repetidamente coincidencias hasta que no quede ninguna. El catchbloque invoca varios PatternSyntaxExceptionmétodos para extraer información útil sobre la excepción. Esta información se emite posteriormente.

No necesita saber más sobre el funcionamiento del código fuente en este momento; quedará claro cuando explore la API en la Parte 2. Sin embargo, debe compilar el Listado 1. Tome el código del Listado 1, luego escriba lo siguiente en su línea de comando para compilar RegexDemo:

javac RegexDemo.java

Patrón y sus construcciones

Pattern, la primera de las tres clases que componen la API Regex, es una representación compilada de una expresión regular. PatternLa documentación del SDK describe varias construcciones de expresiones regulares, pero a menos que ya sea un usuario ávido de expresiones regulares, es posible que algunas partes de la documentación le confundan. ¿Qué son los cuantificadores y cuál es la diferencia entre cuantificadores codiciosos , reacios y posesivos ? ¿Qué son las clases de caracteres , los comparadores de límites , las referencias inversas y las expresiones de banderas incrustadas ? Responderé estas preguntas y más en las próximas secciones.

Cadenas literales

La construcción de expresiones regulares más simple es la cadena literal. Alguna parte del texto de entrada debe coincidir con el patrón de esta construcción para que el patrón coincida correctamente. Considere el siguiente ejemplo:

java RegexDemo apple applet

Este ejemplo intenta descubrir si hay una coincidencia para el applepatrón en el applettexto de entrada. El siguiente resultado revela la coincidencia:

regex = apple input = applet Found [apple] starting at 0 and ending at 4

La salida nos muestra la expresión regular y el texto de entrada, luego indica una coincidencia exitosa de appledentro applet. Además, presenta los índices inicial y final de esa coincidencia: 0y 4, respectivamente. El índice inicial identifica la primera ubicación del texto donde se produce una coincidencia de patrón; el índice final identifica la última ubicación del texto para la coincidencia.

Ahora suponga que especificamos la siguiente línea de comando:

java RegexDemo apple crabapple

Esta vez, obtenemos la siguiente coincidencia con diferentes índices de inicio y finalización:

regex = apple input = crabapple Found [apple] starting at 4 and ending at 8

El escenario inverso, en el que appletes la expresión regular y applees el texto de entrada, no revela ninguna coincidencia. Toda la expresión regular debe coincidir y, en este caso, el texto de entrada no contiene un tdespués apple.

Metacaracteres

Las construcciones de expresiones regulares más potentes combinan caracteres literales con metacaracteres. Por ejemplo, en a.b, el metacarácter de punto ( .) representa cualquier carácter que aparezca entre ay b. Considere el siguiente ejemplo:

java RegexDemo .ox "The quick brown fox jumps over the lazy ox."

Este ejemplo especifica .oxcomo expresión regular y The quick brown fox jumps over the lazy ox.como texto de entrada. RegexDemobusca en el texto coincidencias que comiencen con cualquier carácter y terminen con ox. Produce la siguiente salida:

regex = .ox input = The quick brown fox jumps over the lazy ox. Found [fox] starting at 16 and ending at 18 Found [ ox] starting at 39 and ending at 41

The output reveals two matches: fox and ox (with the leading space character). The . metacharacter matches the f in the first match and the space character in the second match.

What happens when we replace .ox with the period metacharacter? That is, what output results from specifying the following command line:

java RegexDemo . "The quick brown fox jumps over the lazy ox."

Because the period metacharacter matches any character, RegexDemo outputs a match for each character (including the terminating period character) in the input text:

regex = . input = The quick brown fox jumps over the lazy ox. Found [T] starting at 0 and ending at 0 Found [h] starting at 1 and ending at 1 Found [e] starting at 2 and ending at 2 Found [ ] starting at 3 and ending at 3 Found [q] starting at 4 and ending at 4 Found [u] starting at 5 and ending at 5 Found [i] starting at 6 and ending at 6 Found [c] starting at 7 and ending at 7 Found [k] starting at 8 and ending at 8 Found [ ] starting at 9 and ending at 9 Found [b] starting at 10 and ending at 10 Found [r] starting at 11 and ending at 11 Found [o] starting at 12 and ending at 12 Found [w] starting at 13 and ending at 13 Found [n] starting at 14 and ending at 14 Found [ ] starting at 15 and ending at 15 Found [f] starting at 16 and ending at 16 Found [o] starting at 17 and ending at 17 Found [x] starting at 18 and ending at 18 Found [ ] starting at 19 and ending at 19 Found [j] starting at 20 and ending at 20 Found [u] starting at 21 and ending at 21 Found [m] starting at 22 and ending at 22 Found [p] starting at 23 and ending at 23 Found [s] starting at 24 and ending at 24 Found [ ] starting at 25 and ending at 25 Found [o] starting at 26 and ending at 26 Found [v] starting at 27 and ending at 27 Found [e] starting at 28 and ending at 28 Found [r] starting at 29 and ending at 29 Found [ ] starting at 30 and ending at 30 Found [t] starting at 31 and ending at 31 Found [h] starting at 32 and ending at 32 Found [e] starting at 33 and ending at 33 Found [ ] starting at 34 and ending at 34 Found [l] starting at 35 and ending at 35 Found [a] starting at 36 and ending at 36 Found [z] starting at 37 and ending at 37 Found [y] starting at 38 and ending at 38 Found [ ] starting at 39 and ending at 39 Found [o] starting at 40 and ending at 40 Found [x] starting at 41 and ending at 41 Found [.] starting at 42 and ending at 42

Quoting metacharacters

To specify . or any metacharacter as a literal character in a regex construct, quote the metacharacter in one of the following ways:

  • Precede the metacharacter with a backslash character.
  • Place the metacharacter between \Q and \E (e.g., \Q.\E).

Remember to double each backslash character (as in \\. or \\Q.\\E) that appears in a string literal such as String regex = "\\.";. Don't double the backslash character when it appears as part of a command-line argument.

Character classes

We sometimes need to limit characters that will produce matches to a specific character set. For example, we might search text for vowels a, e, i, o, and u, where any occurrence of a vowel indicates a match. A character class identifies a set of characters between square-bracket metacharacters ([ ]), helping us accomplish this task. Pattern supports simple, negation, range, union, intersection, and subtraction character classes. We'll look at all of these below.

Simple character class

The simple character class consists of characters placed side by side and matches only those characters. For example, [abc] matches characters a, b, and c.

Consider the following example:

java RegexDemo [csw] cave

This example matches only c with its counterpart in cave, as shown in the following output:

regex = [csw] input = cave Found [c] starting at 0 and ending at 0

Negation character class

The negation character class begins with the ^ metacharacter and matches only those characters not located in that class. For example, [^abc] matches all characters except a, b, and c.

Consider this example:

java RegexDemo "[^csw]" cave

Note that the double quotes are necessary on my Windows platform, whose shell treats the ^ character as an escape character.

This example matches a, v, and e with their counterparts in cave, as shown here:

regex = [^csw] input = cave Found [a] starting at 1 and ending at 1 Found [v] starting at 2 and ending at 2 Found [e] starting at 3 and ending at 3

Range character class

The range character class consists of two characters separated by a hyphen metacharacter (-). All characters beginning with the character on the left of the hyphen and ending with the character on the right of the hyphen belong to the range. For example, [a-z] matches all lowercase alphabetic characters. It's equivalent to specifying [abcdefghijklmnopqrstuvwxyz].

Consider the following example:

java RegexDemo [a-c] clown

This example matches only c with its counterpart in clown, as shown:

regex = [a-c] input = clown Found [c] starting at 0 and ending at 0

Merging multiple ranges

You can merge multiple ranges into the same range character class by placing them side by side. For example, [a-zA-Z] matches all lowercase and uppercase alphabetic characters.

Union character class

The union character class consists of multiple nested character classes and matches all characters that belong to the resulting union. For example, [a-d[m-p]] matches characters a through d and m through p.

Consider the following example:

java RegexDemo [ab[c-e]] abcdef

This example matches a, b, c, d, and e with their counterparts in abcdef:

regex = [ab[c-e]] input = abcdef Found [a] starting at 0 and ending at 0 Found [b] starting at 1 and ending at 1 Found [c] starting at 2 and ending at 2 Found [d] starting at 3 and ending at 3 Found [e] starting at 4 and ending at 4

Intersection character class

The intersection character class consists of characters common to all nested classes and matches only common characters. For example, [a-z&&[d-f]] matches characters d, e, and f.

Consider the following example:

java RegexDemo "[aeiouy&&[y]]" party

Tenga en cuenta que las comillas dobles son necesarias en mi plataforma Windows, cuyo shell trata el &carácter como un separador de comandos.

Este ejemplo solo coincide ycon su contraparte en party:

regex = [aeiouy&&[y]] input = party Found [y] starting at 4 and ending at 4