Reguläre Ausdrücke in Java, Teil 1: Pattern Matching und die Pattern-Klasse

Javas Zeichen- und verschiedene Zeichenfolgenklassen bieten Unterstützung auf niedriger Ebene für den Mustervergleich, aber diese Unterstützung führt normalerweise zu komplexem Code. Für eine einfachere und effizientere Codierung bietet Java die Regex-API an. Dieses zweiteilige Tutorial hilft Ihnen beim Einstieg in reguläre Ausdrücke und die Regex-API. Zuerst entpacken wir die drei leistungsstarken Klassen im java.util.regexPaket, dann untersuchen wir die PatternKlasse und ihre ausgeklügelten Mustervergleichskonstrukte.

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Was sind reguläre Ausdrücke?

Ein regulärer Ausdruck , auch als Regex oder Regexp bezeichnet , ist eine Zeichenfolge, deren Muster (Vorlage) eine Reihe von Zeichenfolgen beschreibt. Das Muster bestimmt, welche Zeichenfolgen zum Satz gehören. Ein Muster besteht aus wörtlichen Zeichen und Metazeichen , bei denen es sich um Zeichen handelt, die anstelle einer wörtlichen Bedeutung eine besondere Bedeutung haben.

Beim Mustervergleich wird nach Text gesucht, um Übereinstimmungen oder Zeichenfolgen zu identifizieren , die mit dem Muster eines regulären Ausdrucks übereinstimmen. Java unterstützt den Mustervergleich über seine Regex-API. Die API besteht aus drei Klassen Pattern- ,, Matcherund PatternSyntaxException-, die sich alle im java.util.regexPaket befinden:

  • PatternObjekte, auch Muster genannt , sind regulierte Regexe.
  • MatcherObjekte oder Matcher sind Engines, die Muster interpretieren, um Übereinstimmungen in Zeichenfolgen zu finden (Objekte, deren Klassen die java.lang.CharSequenceSchnittstelle implementieren und als Textquellen dienen).
  • PatternSyntaxException Objekte beschreiben illegale Regex-Muster.

Java bietet auch Unterstützung für den Mustervergleich über verschiedene Methoden in seiner java.lang.StringKlasse. Gibt beispielsweise nur dannboolean matches(String regex) true zurück, wenn die aufrufende Zeichenfolge genau mit regexdem regulären Ausdruck übereinstimmt .

Convenience-Methoden

Hinter den Kulissen, matches()und String‚s andere Regex orientierten Komfortmethoden sind im Hinblick auf die Regex API implementiert.

RegexDemo

Ich habe das erstellte RegexDemoAnwendung Java reguläre Ausdrücke und die verschiedenen Methoden in der Lage zu zeigen Pattern, Matcherund PatternSyntaxExceptionKlassen. Hier ist der Quellcode für die Demo:

Listing 1. Demonstration von regulären Ausdrücken

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()); } } }

Das erste , was RegexDemo‚s main()Methode tut , ist seine Befehlszeile zu validieren. Dies erfordert zwei Argumente: Das erste Argument ist ein regulärer Ausdruck, und das zweite Argument ist ein Eingabetext, der mit dem regulären Ausdruck abgeglichen werden soll.

Möglicherweise möchten Sie ein neues Zeilenzeichen ( \n) als Teil des Eingabetextes angeben . Die einzige Möglichkeit, dies zu erreichen, besteht darin, ein \Zeichen gefolgt von einem nZeichen anzugeben . main()konvertiert diese Zeichenfolge in den Unicode-Wert 10.

Der Großteil des RegexDemoCodes befindet sich im Konstrukt try- catch. Der tryBlock gibt zuerst den angegebenen regulären Ausdruck und den eingegebenen Text aus und erstellt dann ein PatternObjekt, in dem der kompilierte reguläre Ausdruck gespeichert ist. (Regexes werden kompiliert, um die Leistung während des Mustervergleichs zu verbessern.) Ein Matcher wird aus dem PatternObjekt extrahiert und wiederholt nach Übereinstimmungen gesucht, bis keine mehr vorhanden sind. Der catchBlock ruft verschiedene PatternSyntaxExceptionMethoden auf, um nützliche Informationen über die Ausnahme zu extrahieren. Diese Informationen werden anschließend ausgegeben.

Sie müssen an dieser Stelle nicht mehr über die Funktionsweise des Quellcodes wissen. Dies wird deutlich, wenn Sie die API in Teil 2 untersuchen. Sie müssen jedoch Listing 1 kompilieren. Holen Sie sich den Code aus Listing 1 und geben Sie Folgendes in Ihre Befehlszeile ein, um ihn zu kompilieren RegexDemo:

javac RegexDemo.java

Muster und seine Konstrukte

Pattern, die erste von drei Klassen, die die Regex-API umfasst, ist eine kompilierte Darstellung eines regulären Ausdrucks. PatternIn der SDK-Dokumentation werden verschiedene Regex-Konstrukte beschrieben. Wenn Sie jedoch noch kein begeisterter Regex-Benutzer sind, werden Sie möglicherweise durch Teile der Dokumentation verwirrt. Was sind Quantifizierer und was ist der Unterschied zwischen gierigen , widerstrebenden und besitzergreifenden Quantifizierern? Was sind Zeichenklassen , Grenzübereinstimmungen , Rückverweise und eingebettete Flag-Ausdrücke ? Ich werde diese und weitere Fragen in den nächsten Abschnitten beantworten.

Wörtliche Zeichenfolgen

Das einfachste Regex-Konstrukt ist die Literalzeichenfolge. Ein Teil des Eingabetextes muss mit dem Muster dieses Konstrukts übereinstimmen, um eine erfolgreiche Musterübereinstimmung zu erzielen. Betrachten Sie das folgende Beispiel:

java RegexDemo apple applet

In diesem Beispiel wird versucht festzustellen, ob das appleMuster im appletEingabetext übereinstimmt . Die folgende Ausgabe zeigt die Übereinstimmung:

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

Die Ausgabe zeigt uns den regulären Ausdruck und den Eingabetext und zeigt dann eine erfolgreiche Übereinstimmung von appleinnen an applet. Darüber hinaus stellt sie die Start- und End - Indizes dieser Partie: 0und 4, respectively. Der Startindex gibt den ersten Textort an, an dem eine Musterübereinstimmung auftritt. Der Endindex gibt den letzten Textort für die Übereinstimmung an.

Angenommen, wir geben die folgende Befehlszeile an:

java RegexDemo apple crabapple

Dieses Mal erhalten wir die folgende Übereinstimmung mit verschiedenen Start- und Endindizes:

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

Das umgekehrte Szenario, bei dem appletes sich um den regulären Ausdruck und appleden Eingabetext handelt, zeigt keine Übereinstimmung. Die gesamte Regex müssen übereinstimmen, und in diesem Fall wird der Eingabetext enthält keine tnach apple.

Metazeichen

Leistungsstärkere Regex-Konstrukte kombinieren wörtliche Zeichen mit Metazeichen. In steht beispielsweise a.bder Punkt metacharacter ( .) für ein beliebiges Zeichen, das zwischen aund angezeigt wird b. Betrachten Sie das folgende Beispiel:

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

In diesem Beispiel wird .oxals regulärer Ausdruck und The quick brown fox jumps over the lazy ox.als Eingabetext angegeben. RegexDemoDurchsucht den Text nach Übereinstimmungen, die mit einem beliebigen Zeichen beginnen und mit enden ox. Es wird die folgende Ausgabe erzeugt:

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

Beachten Sie, dass die doppelten Anführungszeichen auf meiner Windows-Plattform erforderlich sind, deren Shell das &Zeichen als Befehlstrennzeichen behandelt .

Dieses Beispiel stimmt nur ymit seinem Gegenstück in überein party:

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