Eine Anleitung zur Java-API für reguläre Ausdrücke

1. Überblick

In diesem Artikel werden die Java Regex-API und die Verwendung regulärer Ausdrücke in der Java-Programmiersprache beschrieben.

In der Welt der regulären Ausdrücke gibt es viele verschiedene Geschmacksrichtungen, darunter grep, Perl, Python, PHP, awk und vieles mehr.

Dies bedeutet, dass ein regulärer Ausdruck, der in einer Programmiersprache funktioniert, möglicherweise nicht in einer anderen funktioniert. Die Syntax für reguläre Ausdrücke in Java ist der in Perl gefundenen am ähnlichsten.

2. Konfiguration

Um reguläre Ausdrücke in Java verwenden zu können, benötigen wir keine besonderen Einstellungen.

Das JDK enthält ein spezielles Paket java.util.regex , das vollständig für reguläre Ausdrücke vorgesehen ist. Wir müssen es nur in unseren Code importieren.

Darüber hinaus verfügt die java.lang.String -Klasse auch über eine eingebaute Regex-Unterstützung, die wir häufig in unserem Code verwenden.

3. Java Regex-Paket

Das Paket java.util.regex besteht aus drei Klassen: Pattern, Matcher und PatternSyntaxException .

  • Pattern -Objekt ist eine kompilierte Regex. Die Pattern -Klasse enthält nein

öffentliche Konstrukteure. Um ein Muster zu erstellen, müssen Sie zuerst eine seiner öffentlichen statischen compile -Methoden aufrufen, die dann ein Pattern -Objekt zurückgibt. Diese Methoden akzeptieren einen regulären Ausdruck als erstes Argument.

  • Matcher object interpretiert das Muster und führt Übereinstimmungsoperationen durch

gegen eine Eingabe String . Es definiert auch keine öffentlichen Konstruktoren. Wir erhalten ein Matcher -Objekt durch Aufrufen der matcher -Methode für ein Pattern -Objekt.

  • PatternSyntaxException object ist eine nicht geprüfte Ausnahme

gibt einen Syntaxfehler in einem Muster für reguläre Ausdrücke an.

Wir werden diese Klassen ausführlich untersuchen. Wir müssen jedoch zuerst verstehen, wie ein Regex in Java erstellt wird.

Wenn Sie Regex bereits aus einer anderen Umgebung kennen, werden Sie möglicherweise gewisse Unterschiede feststellen, die jedoch minimal sind.

4. Einfaches Beispiel

  • Beginnen wir mit dem einfachsten Anwendungsfall für einen Regex. ** Wie bereits erwähnt, kann ein Regex, wenn er auf einen String angewendet wird, null oder mehrmals übereinstimmen.

Die grundlegendste Form des Musterabgleichs, die von der java.util.regex -API unterstützt wird, ist die Übereinstimmung eines String -Literal . Wenn der reguläre Ausdruck beispielsweise foo ist und die Eingabe String foo ist, ist die Übereinstimmung erfolgreich, da die Strings identisch sind:

@Test
public void givenText__whenSimpleRegexMatches__thenCorrect() {
    Pattern pattern = Pattern.compile("foo");
    Matcher matcher = pattern.matcher("foo");

    assertTrue(matcher.find());
}

Zuerst erstellen wir ein Pattern -Objekt, indem wir seine statische compile -Methode aufrufen und ein Muster übergeben, das wir verwenden möchten.

Dann erstellen wir ein Matcher -Objekt, indem wir die matcher -Methode des Pattern -Objekts aufrufen und den Text übergeben, den wir auf Übereinstimmungen überprüfen möchten.

Danach rufen wir die Methode find im Matcher-Objekt auf.

Die Methode find durchläuft den eingegebenen Text ständig und gibt für jede Übereinstimmung true zurück, sodass wir sie auch verwenden können, um die Übereinstimmungsanzahl zu ermitteln:

@Test
public void givenText__whenSimpleRegexMatchesTwice__thenCorrect() {
    Pattern pattern = Pattern.compile("foo");
    Matcher matcher = pattern.matcher("foofoo");
    int matches = 0;
    while (matcher.find()) {
        matches++;
    }

    assertEquals(matches, 2);
}

Da wir mehr Tests ausführen werden, können wir die Logik für das Finden der Anzahl von Übereinstimmungen in einer Methode namens runTest abstrahieren:

public static int runTest(String regex, String text) {
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    int matches = 0;
    while (matcher.find()) {
        matches++;
    }
    return matches;
}

Wenn wir 0 Übereinstimmungen erhalten, sollte der Test fehlschlagen, andernfalls sollte er bestehen.

5. Metazeichen

Metazeichen beeinflussen die Art und Weise, wie ein Muster abgeglichen wird, und fügen so dem Suchmuster Logik hinzu. Die Java-API unterstützt mehrere Metazeichen, wobei der Punkt "." Am einfachsten ist, der einem beliebigen Zeichen entspricht:

@Test
public void givenText__whenMatchesWithDotMetach__thenCorrect() {
    int matches = runTest(".", "foo");

    assertTrue(matches > 0);
}

Betrachten Sie das vorherige Beispiel, in dem regex foo zweimal mit dem Text foo sowie foofoo übereinstimmte. Wenn wir das Punkt-Metazeichen in der Regex verwenden, würden wir im zweiten Fall keine zwei Übereinstimmungen erhalten:

@Test
public void givenRepeatedText__whenMatchesOnceWithDotMetach__thenCorrect() {
    int matches= runTest("foo.", "foofoo");

    assertEquals(matches, 1);
}

Beachten Sie den Punkt hinter dem foo in der Regex. Der Matcher stimmt mit jedem Text überein, vor dem foo steht, da der letzte Punktteil ein beliebiges Zeichen bedeutet. Nachdem Sie den ersten foo gefunden haben, wird der Rest als beliebiger Buchstabe angesehen. Deshalb gibt es nur ein einziges Spiel.

Die API unterstützt mehrere andere Metazeichen <([\ {\ ^ - = $! |]})? ** +.> , Auf die in diesem Artikel näher eingegangen wird.

6. Zeichenklassen

Beim Durchsuchen der offiziellen Pattern -Klassenspezifikation werden Zusammenfassungen der unterstützten Regex-Konstrukte gefunden. Unter Zeichenklassen haben wir ungefähr 6 Konstrukte.

6.1. ODER Klasse

Konstruiert als [abc] . Jedes der Elemente in der Gruppe stimmt überein:

@Test
public void givenORSet__whenMatchesAny__thenCorrect() {
    int matches = runTest("[abc]", "b");

    assertEquals(matches, 1);
}

Wenn sie alle im Text vorkommen, werden sie unabhängig von der Reihenfolge einzeln abgeglichen:

@Test
public void givenORSet__whenMatchesAnyAndAll__thenCorrect() {
    int matches = runTest("[abc]", "cab");

    assertEquals(matches, 3);
}

Sie können auch als Teil eines String abgewechselt werden. Wenn wir im folgenden Beispiel verschiedene Wörter erstellen, indem wir den ersten Buchstaben mit jedem Element des Satzes abwechseln, stimmen alle überein:

@Test
public void givenORSet__whenMatchesAllCombinations__thenCorrect() {
    int matches = runTest("[bcr]at", "bat cat rat");

    assertEquals(matches, 3);
}

6.2. NOR-Klasse

Der obige Satz wird negiert, indem ein Caret als erstes Element hinzugefügt wird:

@Test
public void givenNORSet__whenMatchesNon__thenCorrect() {
    int matches = runTest("[^abc]", "g");

    assertTrue(matches > 0);
}

Ein anderer Fall:

@Test
public void givenNORSet__whenMatchesAllExceptElements__thenCorrect() {
    int matches = runTest("[^bcr]at", "sat mat eat");

    assertTrue(matches > 0);
}

6.3. Range Class

Wir können eine Klasse definieren, die einen Bereich angibt, in den der übereinstimmende Text mit einem Bindestrich (-) fallen soll. Ebenso können wir einen Bereich auch negieren.

Passende Großbuchstaben:

@Test
public void givenUpperCaseRange__whenMatchesUpperCase__
  thenCorrect() {
    int matches = runTest(
      "[A-Z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 2);
}

Passende Kleinbuchstaben:

@Test
public void givenLowerCaseRange__whenMatchesLowerCase__
  thenCorrect() {
    int matches = runTest(
      "[a-z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 26);
}

Gleiche Groß- und Kleinschreibung:

@Test
public void givenBothLowerAndUpperCaseRange__
  whenMatchesAllLetters__thenCorrect() {
    int matches = runTest(
      "[a-zA-Z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 28);
}

Zu einem bestimmten Zahlenbereich passen:

@Test
public void givenNumberRange__whenMatchesAccurately__
  thenCorrect() {
    int matches = runTest(
      "[1-5]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 2);
}

Zu einem anderen Zahlenbereich passen:

@Test
public void givenNumberRange__whenMatchesAccurately__
  thenCorrect2(){
    int matches = runTest(
      "[30-35]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 1);
}
  • 6.4. Unionsklasse **

Eine Union-Zeichenklasse ist das Ergebnis der Kombination von zwei oder mehr Zeichenklassen:

@Test
public void givenTwoSets__whenMatchesUnion__thenCorrect() {
    int matches = runTest("[1-3[7-9]]", "123456789");

    assertEquals(matches, 6);
}

Der obige Test entspricht nur 6 von 9 Ganzzahlen, da die Vereinigungsmenge 3, 4 und 5 überspringt.

6.5. Kreuzungsklasse

Ähnlich wie die Union-Klasse ergibt sich diese Klasse aus der Auswahl gemeinsamer Elemente aus zwei oder mehr Mengen. Um Schnittpunkte anzuwenden, verwenden wir die __

@Test
public void givenTwoSets__whenMatchesIntersection__thenCorrect() {
    int matches = runTest("[1-6&&[3-9]]", "123456789");

    assertEquals(matches, 4);
}

Wir erhalten 4 Treffer, da der Schnittpunkt der beiden Sätze nur 4 Elemente hat.

6.6. Subtraktionsklasse

Wir können die Subtraktion verwenden, um eine oder mehrere Zeichenklassen zu negieren, z. B. einen Satz ungerader Dezimalzahlen:

@Test
public void givenSetWithSubtraction__whenMatchesAccurately__thenCorrect() {
    int matches = runTest("[0-9&&[^2468]]", "123456789");

    assertEquals(matches, 5);
}

Nur 1,3,5,7,9 wird abgeglichen.

7. Vordefinierte Zeichenklassen

Die Java-Regex-API akzeptiert auch vordefinierte Zeichenklassen. Einige der obigen Zeichenklassen können in kürzerer Form ausgedrückt werden, ohne dass der Code dadurch weniger intuitiv wird. Ein besonderer Aspekt der Java-Version dieser Regex ist das Escape-Zeichen.

Wie wir sehen werden, beginnen die meisten Zeichen mit einem Backslash, der in Java eine besondere Bedeutung hat. Damit diese von der Pattern -Klasse kompiliert werden, muss der führende Backslash mit Escapezeichen versehen werden, d. H. \ D wird zu \\ d .

Übereinstimmende Ziffern, entsprechend [0-9] :

@Test
public void givenDigits__whenMatches__thenCorrect() {
    int matches = runTest("\\d", "123");

    assertEquals(matches, 3);
}

Übereinstimmende Nicht-Ziffern, entsprechend __[^ 0-9]

@Test
public void givenNonDigits__whenMatches__thenCorrect() {
    int mathces = runTest("\\D", "a6c");

    assertEquals(matches, 2);
}

Passender Leerraum:

@Test
public void givenWhiteSpace__whenMatches__thenCorrect() {
    int matches = runTest("\\s", "a c");

    assertEquals(matches, 1);
}

Übereinstimmende nicht weiße Fläche:

@Test
public void givenNonWhiteSpace__whenMatches__thenCorrect() {
    int matches = runTest("\\S", "a c");

    assertEquals(matches, 2);
}

Übereinstimmung mit einem Wortzeichen, äquivalent zu [a-zA-Z 0-9]__:

@Test
public void givenWordCharacter__whenMatches__thenCorrect() {
    int matches = runTest("\\w", "hi!");

    assertEquals(matches, 2);
}

Übereinstimmung mit einem Nicht-Wort-Zeichen:

@Test
public void givenNonWordCharacter__whenMatches__thenCorrect() {
    int matches = runTest("\\W", "hi!");

    assertEquals(matches, 1);
}

8. Quantifizierer

Die Java-Regex-API ermöglicht auch die Verwendung von Quantifizierern. Auf diese Weise können wir das Verhalten des Matches weiter optimieren, indem wir die Anzahl der Ereignisse angeben, mit denen verglichen werden soll.

Um einen Text null oder einmal zu finden, verwenden wir den Quantifizierer ? :

@Test
public void givenZeroOrOneQuantifier__whenMatches__thenCorrect() {
    int matches = runTest("\\a?", "hi");

    assertEquals(matches, 3);
}

Alternativ können wir die Klammer-Syntax verwenden, die auch von der Java-Regex-API unterstützt wird:

@Test
public void givenZeroOrOneQuantifier__whenMatches__thenCorrect2() {
    int matches = runTest("\\a{0,1}", "hi");

    assertEquals(matches, 3);
}

Dieses Beispiel führt das Konzept der Übereinstimmungen mit der Länge Null ein. Wenn also ein Übereinstimmungsschwellwert eines Quantifizierers gleich Null ist, stimmt er immer mit allem im Text überein, einschließlich eines leeren String am Ende jeder Eingabe. Dies bedeutet, dass selbst wenn die Eingabe leer ist, eine Übereinstimmung mit einer Länge von null zurückgegeben wird.

Dies erklärt, warum wir im obigen Beispiel 3 Übereinstimmungen erhalten, obwohl wir einen S-String der Länge zwei haben. Die dritte Übereinstimmung ist leer null String .

Um einen Null- oder unbegrenzten Text abgleichen zu können, verwenden wir ** Quantifizierer.

@Test
public void givenZeroOrManyQuantifier__whenMatches__thenCorrect() {
     int matches = runTest("\\a** ", "hi");

     assertEquals(matches, 3);
}

Unterstützte Alternative:

@Test
public void givenZeroOrManyQuantifier__whenMatches__thenCorrect2() {
    int matches = runTest("\\a{0,}", "hi");

    assertEquals(matches, 3);
}

Der Quantifizierer mit einem Unterschied ist, er hat eine passende Schwelle von 1.

Wenn der erforderliche String -Wert überhaupt nicht auftritt, gibt es keine Übereinstimmung, nicht einmal einen String mit der Länge Null:

@Test
public void givenOneOrManyQuantifier__whenMatches__thenCorrect() {
    int matches = runTest("\\a+", "hi");

    assertFalse(matches);
}

Unterstützte Alternative:

@Test
public void givenOneOrManyQuantifier__whenMatches__thenCorrect2() {
    int matches = runTest("\\a{1,}", "hi");

    assertFalse(matches);
}

Wie in Perl und anderen Sprachen kann die Klammer-Syntax verwendet werden, um einen bestimmten Text mehrmals zuzuordnen:

@Test
public void givenBraceQuantifier__whenMatches__thenCorrect() {
    int matches = runTest("a{3}", "aaaaaa");

    assertEquals(matches, 2);
}

Im obigen Beispiel erhalten wir zwei Übereinstimmungen, da eine Übereinstimmung nur dann auftritt, wenn a dreimal hintereinander erscheint. Im nächsten Test erhalten wir jedoch keine Übereinstimmung, da der Text nur zweimal hintereinander erscheint:

@Test
public void givenBraceQuantifier__whenFailsToMatch__thenCorrect() {
    int matches = runTest("a{3}", "aa");

    assertFalse(matches > 0);
}

Wenn wir einen Bereich in der Klammer verwenden, ist das Spiel gierig und passt vom oberen Ende des Bereichs:

@Test
public void givenBraceQuantifierWithRange__whenMatches__thenCorrect() {
    int matches = runTest("a{2,3}", "aaaa");

    assertEquals(matches, 1);
}

Wir haben mindestens zwei Vorkommnisse angegeben, aber nicht mehr als drei. Daher erhalten wir stattdessen ein einzelnes Match, bei dem der Matcher ein einzelnes aaa und a eins für eins sieht, das nicht zugeordnet werden kann.

Die API ermöglicht es uns jedoch, einen trägen oder widerstrebenden Ansatz anzugeben, so dass der Matcher am unteren Ende des Bereichs beginnen kann. In diesem Fall müssen zwei Vorkommen wie aa und aa abgeglichen werden:

@Test
public void givenBraceQuantifierWithRange__whenMatchesLazily__thenCorrect() {
    int matches = runTest("a{2,3}?", "aaaa");

    assertEquals(matches, 2);
}

9. Gruppen erfassen

Die API ermöglicht es uns auch, mehrere Zeichen als eine Einheit zu behandeln, indem Gruppen erfasst werden.

Es fügt Nummern an die Erfassungsgruppen an und ermöglicht die Rückverweisung mithilfe dieser Nummern.

In diesem Abschnitt werden einige Beispiele zur Verwendung von Erfassungsgruppen in der Java-Regex-API beschrieben.

Verwenden Sie eine Erfassungsgruppe, die nur übereinstimmt, wenn ein Eingabetext zwei Ziffern nebeneinander enthält:

@Test
public void givenCapturingGroup__whenMatches__thenCorrect() {
    int maches = runTest("(\\d\\d)", "12");

    assertEquals(matches, 1);
}

Die an die obige Übereinstimmung angehängte Zahl ist 1 , wobei dem Abgleicher eine Rückwärtsreferenz mitteilt, dass wir ein anderes Vorkommen des übereinstimmenden Teils des Textes abgleichen möchten. Auf diese Weise anstelle von:

@Test
public void givenCapturingGroup__whenMatches__thenCorrect2() {
    int matches = runTest("(\\d\\d)", "1212");

    assertEquals(matches, 2);
}

Wenn es zwei separate Übereinstimmungen für die Eingabe gibt, können wir eine Übereinstimmung haben, die jedoch die gleiche Regex-Übereinstimmung ausbreitet, um die gesamte Länge der Eingabe durch Rückverweisen zu überbrücken:

@Test
public void givenCapturingGroup__whenMatchesWithBackReference__
  thenCorrect() {
    int matches = runTest("(\\d\\d)\\1", "1212");

    assertEquals(matches, 1);
}

Wo müssten wir den Regex ohne Rückverweis wiederholen, um das gleiche Ergebnis zu erzielen:

@Test
public void givenCapturingGroup__whenMatches__thenCorrect3() {
    int matches = runTest("(\\d\\d)(\\d\\d)", "1212");

    assertEquals(matches, 1);
}

Bei einer anderen Anzahl von Wiederholungen kann der Abgleicher durch Rückverweise die Eingabe als eine einzige Übereinstimmung anzeigen:

@Test
public void givenCapturingGroup__whenMatchesWithBackReference__
  thenCorrect2() {
    int matches = runTest("(\\d\\d)\\1\\1\\1", "12121212");

    assertEquals(matches, 1);
}

Wenn Sie jedoch auch die letzte Ziffer ändern, schlägt die Übereinstimmung fehl:

@Test
public void givenCapturingGroupAndWrongInput__
  whenMatchFailsWithBackReference__thenCorrect() {
    int matches = runTest("(\\d\\d)\\1", "1213");

    assertFalse(matches > 0);
}

Es ist wichtig, die Escape-Backslashes nicht zu vergessen, dies ist für die Java-Syntax von entscheidender Bedeutung.

10. Boundary Matchers

Die Java-Regex-API unterstützt auch den Boundary-Matching. Wenn es uns wichtig ist, wo genau der eingegebene Text erscheinen soll, dann suchen wir das. Bei den vorherigen Beispielen haben wir uns nur darum gekümmert, ob eine Übereinstimmung gefunden wurde oder nicht.

Um nur übereinzustimmen, wenn der erforderliche Regex am Anfang des Textes wahr ist, verwenden wir das Caret

Dieser Test schlägt fehl, da der Text dog am Anfang steht:

@Test
public void givenText__whenMatchesAtBeginning__thenCorrect() {
    int matches = runTest("^dog", "dogs are friendly");

    assertTrue(matches > 0);
}

Der folgende Test schlägt fehl:

@Test
public void givenTextAndWrongInput__whenMatchFailsAtBeginning__
  thenCorrect() {
    int matches = runTest("^dog", "are dogs are friendly?");

    assertFalse(matches > 0);
}

Um nur zu passen, wenn der erforderliche Ausdruck am Ende des Textes wahr ist, verwenden wir das Dollarzeichen __ $.

@Test
public void givenText__whenMatchesAtEnd__thenCorrect() {
    int matches = runTest("dog$", "Man's best friend is a dog");

    assertTrue(matches > 0);
}

Und hier wird keine Übereinstimmung gefunden:

@Test
public void givenTextAndWrongInput__whenMatchFailsAtEnd__thenCorrect() {
    int matches = runTest("dog$", "is a dog man's best friend?");

    assertFalse(matches > 0);
}

Wenn wir nur dann eine Übereinstimmung wünschen, wenn der erforderliche Text an einer Wortgrenze gefunden wird, verwenden wir \\ b regex am Anfang und am Ende der Regex:

Raum ist eine Wortgrenze:

@Test
public void givenText__whenMatchesAtWordBoundary__thenCorrect() {
    int matches = runTest("\\bdog\\b", "a dog is friendly");

    assertTrue(matches > 0);
}

Die leere Zeichenfolge am Anfang einer Zeile ist auch eine Wortgrenze:

@Test
public void givenText__whenMatchesAtWordBoundary__thenCorrect2() {
    int matches = runTest("\\bdog\\b", "dog is man's best friend");

    assertTrue(matches > 0);
}

Diese Tests bestehen, weil der Anfang eines String sowie der Abstand zwischen einem Text und einem anderen eine Wortgrenze markiert. Der folgende Test zeigt jedoch das Gegenteil:

@Test
public void givenWrongText__whenMatchFailsAtWordBoundary__thenCorrect() {
    int matches = runTest("\\bdog\\b", "snoop dogg is a rapper");

    assertFalse(matches > 0);
}

Zwei-Wort-Zeichen, die in einer Reihe erscheinen, kennzeichnen keine Wortgrenze, aber wir können es passieren lassen, indem Sie das Ende der Regex ändern, um nach einer Nicht-Wort-Grenze zu suchen:

@Test
public void givenText__whenMatchesAtWordAndNonBoundary__thenCorrect() {
    int matches = runTest("\\bdog\\B", "snoop dogg is a rapper");
    assertTrue(matches > 0);
}

11. Musterklassenmethoden

Bisher haben wir Pattern -Objekte nur auf einfache Weise erstellt.

Diese Klasse hat jedoch eine andere Variante der compile -Methode, die neben dem Regex-Argument eine Reihe von Flags akzeptiert, die die Art und Weise beeinflussen, in der das Muster übereinstimmt.

Diese Flags sind einfach ganzzahlige Werte. Lassen Sie uns die runTest -Methode in der Testklasse überladen, damit sie als drittes Argument ein Flag annehmen kann:

public static int runTest(String regex, String text, int flags) {
    pattern = Pattern.compile(regex, flags);
    matcher = pattern.matcher(text);
    int matches = 0;
    while (matcher.find()){
        matches++;
    }
    return matches;
}

In diesem Abschnitt werden die verschiedenen unterstützten Flags und ihre Verwendung beschrieben.

  • Pattern.CANON EQ__ **

Dieses Flag ermöglicht kanonische Äquivalenz. Wenn angegeben, werden zwei Zeichen als übereinstimmend angesehen, wenn und nur dann, wenn ihre vollständige kanonische Zerlegung übereinstimmt.

Betrachten Sie das akzentuierte Unicode-Zeichen é . Sein zusammengesetzter Codepunkt ist u00E9 . Unicode hat jedoch auch einen separaten Codepunkt für seine Komponentenzeichen e , u0065 und den Akutakzent u0301 . In diesem Fall ist das zusammengesetzte Zeichen u00E9 von der aus zwei Zeichen bestehenden Sequenz u0065 u0301 nicht zu unterscheiden.

Standardmäßig berücksichtigt das Matching keine kanonische Äquivalenz:

@Test
public void givenRegexWithoutCanonEq__whenMatchFailsOnEquivalentUnicode__thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301");

    assertFalse(matches > 0);
}

Wenn wir jedoch die Flagge hinzufügen, wird der Test bestanden:

@Test
public void givenRegexWithCanonEq__whenMatchesOnEquivalentUnicode__thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON__EQ);

    assertTrue(matches > 0);
}

Pattern.CASE INSENSITIVE __

Dieses Flag ermöglicht das Abgleichen unabhängig vom Fall. Standardmäßig berücksichtigt das Matching:

@Test
public void givenRegexWithDefaultMatcher__whenMatchFailsOnDifferentCases__thenCorrect() {
    int matches = runTest("dog", "This is a Dog");

    assertFalse(matches > 0);
}

Mit diesem Flag können wir also das Standardverhalten ändern:

@Test
public void givenRegexWithCaseInsensitiveMatcher
  __whenMatchesOnDifferentCases__thenCorrect() {
    int matches = runTest(
      "dog", "This is a Dog", Pattern.CASE__INSENSITIVE);

    assertTrue(matches > 0);
}

Wir können auch den äquivalenten eingebetteten Flag-Ausdruck verwenden, um das gleiche Ergebnis zu erzielen:

@Test
public void givenRegexWithEmbeddedCaseInsensitiveMatcher
  __whenMatchesOnDifferentCases__thenCorrect() {
    int matches = runTest("(?i)dog", "This is a Dog");

    assertTrue(matches > 0);
}

Pattern.COMMENTS

Mit der Java-API können Kommentare mit # in die Regex eingefügt werden. Dies kann hilfreich sein, um komplexe Regex zu dokumentieren, der für einen anderen Programmierer nicht sofort offensichtlich ist.

Das Kommentar-Flag bewirkt, dass der Matcher Leerzeichen oder Kommentare in der Regex ignoriert und nur das Muster berücksichtigt. Im Standardabgleichmodus würde der folgende Test fehlschlagen:

@Test
public void givenRegexWithComments__whenMatchFailsWithoutFlag__thenCorrect() {
    int matches = runTest(
      "dog$  #check for word dog at end of text", "This is a dog");

    assertFalse(matches > 0);
}

Dies liegt daran, dass der Matcher im Eingabetext nach dem gesamten regulären Ausdruck sucht, einschließlich Leerzeichen und dem Zeichen #. Wenn wir jedoch das Flag verwenden, werden die zusätzlichen Leerzeichen ignoriert, und jeder Text, der mit # beginnt, wird als Kommentar betrachtet, der für jede Zeile ignoriert wird:

@Test
public void givenRegexWithComments__whenMatchesWithFlag__thenCorrect() {
    int matches = runTest(
      "dog$  #check end of text","This is a dog", Pattern.COMMENTS);

    assertTrue(matches > 0);
}

Dafür gibt es auch einen alternativen eingebetteten Flag-Ausdruck:

@Test
public void givenRegexWithComments__whenMatchesWithEmbeddedFlag__thenCorrect() {
    int matches = runTest(
      "(?x)dog$  #check end of text", "This is a dog");

    assertTrue(matches > 0);
}

Pattern.DOTALL

Wenn wir in regex den Punkt "." Verwenden, stimmen wir standardmäßig mit jedem Zeichen in der Eingabe String überein, bis wir ein neues Linienzeichen finden.

Wenn Sie dieses Flag verwenden, enthält das Match auch den Leitungsabschluss. Wir werden die folgenden Beispiele besser verstehen. Diese Beispiele werden etwas anders sein. Da wir daran interessiert sind, gegen den übereinstimmenden String zu bestehen, verwenden wir die matcher ’s group -Methode, die den vorherigen Treffer zurückgibt.

Zuerst sehen wir das Standardverhalten:

@Test
public void givenRegexWithLineTerminator__whenMatchFails__thenCorrect() {
    Pattern pattern = Pattern.compile("(.** )");
    Matcher matcher = pattern.matcher(
      "this is a text" + System.getProperty("line.separator")
        + " continued on another line");
    matcher.find();

    assertEquals("this is a text", matcher.group(1));
}

Wie wir sehen können, wird nur der erste Teil der Eingabe vor dem Zeilenabschluss angepasst.

Im dotall -Modus wird nun der gesamte Text einschließlich des Zeilenabschlusszeichens angepasst:

@Test
public void givenRegexWithLineTerminator__whenMatchesWithDotall__thenCorrect() {
    Pattern pattern = Pattern.compile("(.** )", Pattern.DOTALL);
    Matcher matcher = pattern.matcher(
      "this is a text" + System.getProperty("line.separator")
        + " continued on another line");
    matcher.find();
    assertEquals(
      "this is a text" + System.getProperty("line.separator")
        + " continued on another line", matcher.group(1));
}

Wir können auch einen eingebetteten Flag-Ausdruck verwenden, um den Modus dotall zu aktivieren:

@Test
public void givenRegexWithLineTerminator__whenMatchesWithEmbeddedDotall
  __thenCorrect() {

    Pattern pattern = Pattern.compile("(?s)(.** )");
    Matcher matcher = pattern.matcher(
      "this is a text" + System.getProperty("line.separator")
        + " continued on another line");
    matcher.find();

    assertEquals(
      "this is a text" + System.getProperty("line.separator")
        + " continued on another line", matcher.group(1));
}

Pattern.LITERAL

In diesem Modus gibt der Matcher keine Metazeichen, Escape-Zeichen oder Regex-Syntax an. Ohne dieses Flag wird der Matcher die folgende Regex mit der Eingabe String abgleichen:

@Test
public void givenRegex__whenMatchesWithoutLiteralFlag__thenCorrect() {
    int matches = runTest("(.** )", "text");

    assertTrue(matches > 0);
}

Dies ist das Standardverhalten, das wir in allen Beispielen gesehen haben.

Mit diesem Flag wird jedoch keine Übereinstimmung gefunden, da der Matcher (. ** ) Sucht, anstatt sie zu interpretieren:

@Test
public void givenRegex__whenMatchFailsWithLiteralFlag__thenCorrect() {
    int matches = runTest("(.** )", "text", Pattern.LITERAL);

    assertFalse(matches > 0);
}

Wenn wir nun die erforderliche Zeichenfolge hinzufügen, wird der Test bestanden:

@Test
public void givenRegex__whenMatchesWithLiteralFlag__thenCorrect() {
    int matches = runTest("(.** )", "text(.** )", Pattern.LITERAL);

    assertTrue(matches > 0);
}

Es gibt kein eingebettetes Flag-Zeichen zum Aktivieren der Literal-Analyse.

Pattern.MULTILINE

Standardmäßig stimmen die Metazeichen ^ und $ absolut am Anfang bzw. Ende der gesamten Eingabe String überein. Der Matcher ignoriert alle Leitungsabschlusszeichen:

@Test
public void givenRegex__whenMatchFailsWithoutMultilineFlag__thenCorrect() {
    int matches = runTest(
      "dog$", "This is a dog" + System.getProperty("line.separator")
      + "this is a fox");

    assertFalse(matches > 0);
}

Die Übereinstimmung schlägt fehl, da der Matcher am Ende des gesamten String nach dog sucht, der dog jedoch am Ende der ersten Zeile der Zeichenfolge vorhanden ist.

Mit dem Flag wird derselbe Test jedoch bestanden, da der Matcher jetzt Leitungsabschlusszeichen berücksichtigt. Der String dog wird also kurz vor dem Ende der Zeile gefunden, also Erfolg:

@Test
public void givenRegex__whenMatchesWithMultilineFlag__thenCorrect() {
    int matches = runTest(
      "dog$", "This is a dog" + System.getProperty("line.separator")
      + "this is a fox", Pattern.MULTILINE);

    assertTrue(matches > 0);
}

Hier ist die eingebettete Flag-Version:

@Test
public void givenRegex__whenMatchesWithEmbeddedMultilineFlag__
  thenCorrect() {
    int matches = runTest(
      "(?m)dog$", "This is a dog" + System.getProperty("line.separator")
      + "this is a fox");

    assertTrue(matches > 0);
}

12. Methoden der Matcher-Klasse

In diesem Abschnitt werden einige nützliche Methoden der Matcher -Klasse beschrieben. Wir werden sie aus Gründen der Übersichtlichkeit nach Funktionalität gruppieren.

12.1. Indexmethoden

Indexmethoden liefern nützliche Indexwerte, die genau angeben, wo in der Eingabe String die Übereinstimmung gefunden wurde. Im folgenden Test bestätigen wir die Start- und Endindizes der Übereinstimmung für dog in der Eingabe String :

@Test
public void givenMatch__whenGetsIndices__thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("This dog is mine");
    matcher.find();

    assertEquals(5, matcher.start());
    assertEquals(8, matcher.end());
}

12.2. Lernmethoden

Die Lernmethoden durchlaufen die Eingabe String und geben einen booleschen Wert zurück, der angibt, ob das Muster gefunden wurde oder nicht. Häufig werden die Methoden matches und lookingAt verwendet.

Die Methoden matches und lookingAt versuchen beide, eine Eingabesequenz mit einem Muster abzugleichen. Der Unterschied ist, dass für Matches die gesamte Eingabesequenz abgeglichen werden muss, für lookingAt nicht.

Beide Methoden beginnen am Anfang der Eingabe String :

@Test
public void whenStudyMethodsWork__thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dogs are friendly");

    assertTrue(matcher.lookingAt());
    assertFalse(matcher.matches());
}

Die Matches-Methode gibt in einem Fall wie folgt true zurück:

@Test
public void whenMatchesStudyMethodWorks__thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dog");

    assertTrue(matcher.matches());
}

12.3. Ersatzmethoden

Ersetzungsmethoden sind nützlich, um Text in einer Eingabezeichenfolge zu ersetzen. Die häufigsten sind replaceFirst und replaceAll .

Die Methoden replaceFirst und replaceAll ersetzen den Text, der einem bestimmten regulären Ausdruck entspricht. Wie der Name schon sagt, ersetzt replaceFirst das erste Vorkommen und replaceAll ersetzt alle Vorkommen:

@Test
public void whenReplaceFirstWorks__thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher(
      "dogs are domestic animals, dogs are friendly");
    String newStr = matcher.replaceFirst("cat");

    assertEquals(
      "cats are domestic animals, dogs are friendly", newStr);
}

Ersetzen Sie alle Vorkommen:

@Test
public void whenReplaceAllWorks__thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher(
      "dogs are domestic animals, dogs are friendly");
    String newStr = matcher.replaceAll("cat");

    assertEquals("cats are domestic animals, cats are friendly", newStr);
}

13. Fazit

In diesem Artikel haben wir gelernt, wie reguläre Ausdrücke in Java verwendet werden, und haben auch die wichtigsten Funktionen des Pakets java.util.regex untersucht.

Den vollständigen Quellcode für das Projekt einschließlich aller hier verwendeten Codebeispiele finden Sie im GitHub-Projekt .