Java正則表達(dá)式API指南

1. 概述

在本教程中,我們將討論 Java 正則表達(dá)式 API,以及如何在 Java 編程語言中使用正則表達(dá)式。

在正則表達(dá)式的世界中,有許多不同的風(fēng)格可供選擇,例如 grep、Perl、Python、PHP、awk 等等。

這意味著在一種編程語言中可以工作的正則表達(dá)式,可能在另一種語言中無法工作。Java 中的正則表達(dá)式語法與 Perl 中的語法最為相似。

2. 設(shè)置

在 Java 中使用正則表達(dá)式,我們不需要任何特殊設(shè)置。JDK 包含了一個專門用于正則表達(dá)式操作的包,java.util.regex,只需在代碼中導(dǎo)入即可。

此外,java.lang.String 類也內(nèi)置了正則表達(dá)式的支持,通常在我們的代碼中使用。

3. Java 正則表達(dá)式包

java.util.regex 包由三個類組成:PatternMatcherPatternSyntaxException

  • Pattern 對象是已編譯的正則表達(dá)式。Pattern 類沒有公共構(gòu)造函數(shù)。要創(chuàng)建模式,首先必須調(diào)用其公共靜態(tài)方法 compile,該方法返回一個 Pattern 對象。這些方法接受正則表達(dá)式作為第一個參數(shù)。
  • Matcher 對象解釋模式,并針對輸入字符串執(zhí)行匹配操作。它也沒有公共構(gòu)造函數(shù)。我們通過在 Pattern 對象上調(diào)用 matcher 方法來獲取 Matcher 對象,并傳遞我們想要匹配的文本。
  • PatternSyntaxException 對象是未檢查的異常,表示正則表達(dá)式模式中的語法錯誤。

我們將詳細(xì)探討這些類,但首先必須了解如何在 Java 中構(gòu)造正則表達(dá)式。

如果我們已經(jīng)熟悉了其他環(huán)境中的正則表達(dá)式,可能會發(fā)現(xiàn)一些差異,但它們非常小。

4. 簡單示例

讓我們從最簡單的正則表達(dá)式使用案例開始。如前所述,當(dāng)我們將正則表達(dá)式應(yīng)用于字符串時,它可能會匹配零次或多次。

最基本的模式匹配形式是對字符串字面的匹配。例如,如果正則表達(dá)式是 foo 且輸入字符串是 foo,匹配將成功,因為兩個字符串是相同的:

@Test
public void givenText_whenSimpleRegexMatches_thenCorrect() {
    Pattern pattern = Pattern.compile("foo");
    Matcher matcher = pattern.matcher("foo");
    assertTrue(matcher.find());
}

我們首先通過調(diào)用其靜態(tài)方法 compile 并傳遞要使用的模式,來創(chuàng)建一個 Pattern 對象。

然后我們通過調(diào)用 Pattern 對象的 matcher 方法并傳遞要檢查匹配的文本來創(chuàng)建一個 Matcher 對象。

最后,我們調(diào)用 Matcher 對象中的 find 方法。

find 方法在輸入文本中逐步前進(jìn)并為每次匹配返回 true,因此我們可以使用它來計算匹配的次數(shù):

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

由于我們將運行更多測試,可以將找到匹配次數(shù)的邏輯抽象為一個名為 runTest 的方法:

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

當(dāng)我們得到 0 次匹配時,測試應(yīng)該失??;否則,它應(yīng)該通過。

5. 元字符

元字符影響模式的匹配方式;它們在一定程度上為搜索模式添加了邏輯。Java API 支持幾個元字符,最簡單的就是點“.”,它匹配任何字符:

@Test
public void givenText_whenMatchesWithDotMetach_thenCorrect() {
    int matches = runTest(".", "foo");
    assertTrue(matches > 0);
}

讓我們考慮前面的示例,其中正則表達(dá)式 foo 匹配了文本 foo 以及 foofoo,兩次。如果我們在正則表達(dá)式中使用點元字符,在第二種情況下我們不會得到兩次匹配:

@Test
public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() {
    int matches = runTest("foo.", "foofoo");
    assertEquals(matches, 1);
}

注意正則表達(dá)式中 foo 后面的點。匹配器匹配每個 foo 之后的文本,因為最后的點部分表示任何字符。所以在找到第一個 foo 后,其余部分被視為任意字符。這就是為什么只有一次匹配的原因。

API 還支持其他元字符 <([{\\^-=$!|]})?*+.>,我們將在本文進(jìn)一步探討。

6. 字符類

瀏覽官方的 Pattern 類規(guī)范時,我們會發(fā)現(xiàn)支持的正則表達(dá)式結(jié)構(gòu)的摘要。在字符類下,我們有大約 6 種結(jié)構(gòu)。

6.1 與或類

我們將其構(gòu)造為 [abc]。它匹配集合中的任意一個元素:

@Test
public void givenORSet_whenMatchesAny_thenCorrect() {
    int matches = runTest("[abc]", "b");
    assertEquals(matches, 1);
}

如果它們都出現(xiàn)在文本中,將分別匹配每個元素,而不考慮順序:

@Test
public void givenORSet_whenMatchesAnyAndAll_thenCorrect() {
    int matches = runTest("[abc]", "cab");
    assertEquals(matches, 3);
}

它們也可以作為字符串的一部分進(jìn)行交替。在下面的示例中,當(dāng)我們通過將集合中的每個元素與第一個字母交替創(chuàng)建不同的單詞時,所有這些都匹配:

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

6.2 NOR類

上述集合通過在第一個元素位置添加插入符進(jìn)行否定:

@Test
public void givenNORSet_whenMatchesNon_thenCorrect() {
    int matches = runTest("[^abc]", "g");
 
    assertTrue(matches > 0);
}

這里是另一個例子:

@Test
public void givenNORSet_whenMatchesAllExceptElements_thenCorrect() {
    int matches = runTest("[^bcr]at", "sat mat eat");
 
    assertTrue(matches > 0);
}

6.3  范圍

我們可以通過使用連字符(-)定義一個匹配文本應(yīng)落入的范圍。我們也可以對范圍進(jìn)行否定。

匹配大寫字母:

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

匹配小寫字母:


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

匹配大寫和小寫字母:

@Test
public void givenBothLowerAndUpperCaseRange_
  whenMatchesAllLetters_thenCorrect() {
    int matches = runTest(
      "[a-zA-Z]", "Two Uppercase alphabets 34 overall");
 
    assertEquals(matches, 28);
}

匹配給定的數(shù)字范圍:


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

匹配另一個數(shù)字范圍:

@Test
public void givenNumberRange_whenMatchesAccurately_
  thenCorrect2(){
    int matches = runTest(
      "3[0-5]", "Two Uppercase alphabets 34 overall");
  
    assertEquals(matches, 1);
}

6.4 聯(lián)合類

聯(lián)合字符類是通過組合兩個或更多字符類產(chǎn)生的結(jié)果:

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

上面的測試只會匹配九個整數(shù)中的六個,因為聯(lián)合集跳過了4、5和6。

6.5 交叉點類

類似于聯(lián)合類,這個類是通過從兩個或更多集合中挑選公共元素得到的。要應(yīng)用交集,我們使用&&:

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

我們會得到四個匹配,因為這兩個集合的交集中只有四個元素。

6.6 減法類

我們可以使用減法來否定一個或多個字符類。例如,我們可以匹配一組奇數(shù)十進(jìn)制數(shù):

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

只有1、3、5、7、9會被匹配。

7. 預(yù)定義字符類

Java正則表達(dá)式API還接受預(yù)定義的字符類。上述的一些字符類可以用更簡短的形式表示,雖然這會讓代碼不那么直觀。Java正則表達(dá)式的一個特殊方面是轉(zhuǎn)義字符。

正如我們將看到的,大多數(shù)字符將以反斜杠開頭,這在Java中有特殊的含義。要使這些由Pattern類編譯,前導(dǎo)反斜杠必須被轉(zhuǎn)義,即\\d變?yōu)閈\\\d。

匹配數(shù)字,相當(dāng)于[0-9]:

@Test
public void givenDigits_whenMatches_thenCorrect() {
    int matches = runTest("\\\\d", "123");
 
    assertEquals(matches, 3);
}

匹配非數(shù)字,相當(dāng)于[^0-9]:

@Test
public void givenNonDigits_whenMatches_thenCorrect() {
    int mathces = runTest("\\\\D", "a6c");
 
    assertEquals(matches, 2);
}

匹配空格:

@Test
public void givenWhiteSpace_whenMatches_thenCorrect() {
    int matches = runTest("\\\\s", "a c");
 
    assertEquals(matches, 1);
}

匹配非空格:

@Test
public void givenNonWhiteSpace_whenMatches_thenCorrect() {
    int matches = runTest("\\\\S", "a c");
 
    assertEquals(matches, 2);
}

匹配單詞字符,相當(dāng)于[a-zA-Z_0-9]:


@Test
public void givenWordCharacter_whenMatches_thenCorrect() {
    int matches = runTest("\\\\w", "hi!");
 
    assertEquals(matches, 2);
}

匹配非單詞字符:

@Test
public void givenNonWordCharacter_whenMatches_thenCorrect() {
    int matches = runTest("\\\\W", "hi!");
 
    assertEquals(matches, 1);
}

8. 量詞

Java 正則表達(dá)式 API 也允許我們使用量詞。這使我們可以通過指定匹配的出現(xiàn)次數(shù)來進(jìn)一步調(diào)整匹配的行為。

要匹配文本零次或一次,我們使用?量詞:

@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect() {
    int matches = runTest("\\\\a?", "hi");
    assertEquals(matches, 3);
}

或者,我們可以使用大括號語法,Java 正則表達(dá)式 API 也支持這種語法:

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

這個示例引入了零長度匹配的概念。當(dāng)量詞的匹配閾值為零時,它總是會匹配文本中的所有內(nèi)容,包括每個輸入末尾的空字符串。這意味著即使輸入為空,它也會返回一個零長度的匹配。

這解釋了為什么我們在上述示例中得到了三次匹配,盡管字符串的長度只有兩個。第三次匹配是零長度的空匹配。

要匹配文本零次或無限次,我們使用*量詞,它類似于?:


@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect() {
     int matches = runTest("\\\\a*", "hi");
     assertEquals(matches, 3);
}

支持的替代方案:


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

有區(qū)別的量詞是+,它的匹配閾值為1。如果所需的字符串根本沒有出現(xiàn),則不會有任何匹配,甚至連零長度的字符串也不會匹配:


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

支持的替代方案:


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

與 Perl 和其他語言一樣,我們可以使用大括號語法來匹配特定次數(shù)的文本:


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

在上述示例中,我們得到了兩次匹配,因為只有當(dāng)a連續(xù)出現(xiàn)三次時才會產(chǎn)生匹配。然而,在下一個測試中,我們不會得到匹配,因為文本只連續(xù)出現(xiàn)了兩次:


@Test
public void givenBraceQuantifier_whenFailsToMatch_thenCorrect() {
    int matches = runTest("a{3}", "aa");
    assertFalse(matches > 0);
}

當(dāng)我們在大括號中使用范圍時,匹配將是貪婪的,從范圍的高端開始匹配:


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

在這里我們指定了至少兩次出現(xiàn),但不超過三次,所以我們得到了一次匹配,匹配器看到的是一個aaa和一個孤立的a,它無法匹配。

然而,API 允許我們指定惰性或非貪婪的方法,使得匹配器可以從范圍的低端開始匹配,匹配兩個連續(xù)的aa:


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

9. 捕獲組

API 還允許我們通過捕獲組將多個字符視為一個單元。它會為捕獲組附加編號,并允許使用這些編號進(jìn)行反向引用。

在本節(jié)中,我們將看到一些如何在 Java 正則表達(dá)式 API 中使用捕獲組的示例。

讓我們使用一個捕獲組,僅當(dāng)輸入文本包含兩個相鄰的數(shù)字時才進(jìn)行匹配:


@Test
public void givenCapturingGroup_whenMatches_thenCorrect() {
    int matches = runTest("(\\\\d\\\\d)", "12");
    assertEquals(matches, 1);
}

上面匹配的編號是1,使用反向引用告訴匹配器我們要匹配文本的另一部分。這樣,輸入不會有兩個單獨的匹配:


@Test
public void givenCapturingGroup_whenMatches_thenCorrect2() {
    int matches = runTest("(\\\\d\\\\d)", "1212");
    assertEquals(matches, 2);
}

我們可以獲得一次匹配,但通過反向引用將相同的正則表達(dá)式匹配擴展到整個輸入的長度:


@Test
public void givenCapturingGroup_whenMatchesWithBackReference_thenCorrect() {
    int matches = runTest("(\\\\d\\\\d)\\\\1", "1212");
    assertEquals(matches, 1);
}

我們必須重復(fù)正則表達(dá)式而不使用反向引用才能實現(xiàn)相同的效果:


@Test
public void givenCapturingGroup_whenMatches_thenCorrect3() {
    int matches = runTest("(\\\\d\\\\d)(\\\\d\\\\d)", "1212");
    assertEquals(matches, 1);
}

同樣,對于任何其他數(shù)量的重復(fù),反向引用可以使匹配器將輸入視為一次匹配:


@Test
public void givenCapturingGroup_whenMatchesWithBackReference_thenCorrect2() {
    int matches = runTest("(\\\\d\\\\d)\\\\1\\\\1\\\\1", "12121212");
    assertEquals(matches, 1);
}

但如果我們更改最后一個數(shù)字,匹配將失?。?/p>


@Test
public void givenCapturingGroupAndWrongInput_whenMatchFailsWithBackReference_thenCorrect() {
    int matches = runTest("(\\\\d\\\\d)\\\\1", "1213");
    assertFalse(matches > 0);
}

請務(wù)必記住轉(zhuǎn)義反斜杠,它們在 Java 語法中至關(guān)重要。

10. 邊界匹配器

Java 正則表達(dá)式 API 也支持邊界匹配。如果我們關(guān)心匹配應(yīng)該出現(xiàn)在輸入文本的確切位置,那么這就是我們要找的。在前面的示例中,我們關(guān)心的只是是否找到了匹配。

要僅在所需正則表達(dá)式在文本開頭為真時匹配,我們使用插入符號^。

由于文本dog可以在開頭找到,這個測試將通過:


@Test
public void givenText_whenMatchesAtBeginning_thenCorrect() {
    int matches = runTest("^dog", "dogs are friendly");
    assertTrue(matches > 0);
}

以下測試將失敗:


@Test
public void givenTextAndWrongInput_whenMatchFailsAtBeginning_thenCorrect() {
    int matches = runTest("^dog", "are dogs are friendly?");
    assertFalse(matches > 0);
}

要僅在所需正則表達(dá)式在文本末尾為真時匹配,我們使用美元符號$。我們將在以下情況下找到匹配:


@Test
public void givenText_whenMatchesAtEnd_thenCorrect() {
    int matches = runTest("dog$", "Man's best friend is a dog");
    assertTrue(matches > 0);
}

而在這里我們不會找到匹配:


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

如果我們只想在找到所需文本時進(jìn)行匹配,我們在正則表達(dá)式的開頭和結(jié)尾使用\\b:

空格是一個單詞邊界:


@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect() {
    int matches = runTest("\\bdog\\b", "a dog is friendly");
    assertTrue(matches > 0);
}

而這個測試將失?。?/p>


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

類似地,我們可以匹配整個文本的邊界,而不僅僅是單詞的邊界:


@Test
public void givenText_whenMatchesAtWordAndTextBoundary_thenCorrect() {
    int matches = runTest("^dog$", "dog");
    assertTrue(matches > 0);
}

11. Pattern 類方法

之前,我們只用基本方式創(chuàng)建了 Pattern 對象。然而,這個類還有一個編譯方法的變體,它接受一組標(biāo)志以及正則表達(dá)式參數(shù),這會影響我們匹配模式的方式。

這些標(biāo)志只是抽象的整數(shù)值。讓我們重載測試類中的 runTest 方法,使它可以將標(biāo)志作為第三個參數(shù):

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

在本節(jié)中,我們將介紹不同支持的標(biāo)志及其用法。

Pattern.CANON_EQ

此標(biāo)志啟用了規(guī)范等效性。指定時,只有當(dāng)兩個字符的完整規(guī)范分解匹配時,它們才被視為匹配。

考慮帶重音符號的 Unicode 字符é。其復(fù)合代碼點是 u00E9。然而,Unicode 也有其組件字符的獨立代碼點:eu0065)和重音符號 u0301。在這種情況下,復(fù)合字符 u00E9 與字符序列 u0065u0301 無法區(qū)分。

默認(rèn)情況下,匹配不考慮規(guī)范等效性:

@Test
public void givenRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301");
    assertFalse(matches > 0);
}

但如果我們添加了標(biāo)志,則測試將通過:

@Test
public void givenRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON_EQ);
    assertTrue(matches > 0);
}

Pattern.CASE_INSENSITIVE

此標(biāo)志允許忽略大小寫進(jìn)行匹配。默認(rèn)情況下,匹配時考慮大小寫:

@Test
public void givenRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect() {
    int matches = runTest("dog", "This is a Dog");
    assertFalse(matches > 0);
}

使用此標(biāo)志后,我們可以改變默認(rèn)行為:

@Test
public void givenRegexWithCaseInsensitiveMatcher_whenMatchesOnDifferentCases_thenCorrect() {
    int matches = runTest("dog", "This is a Dog", Pattern.CASE_INSENSITIVE);
    assertTrue(matches > 0);
}

我們還可以使用等效的嵌入標(biāo)志表達(dá)式來實現(xiàn)相同的效果:

@Test
public void givenRegexWithEmbeddedCaseInsensitiveMatcher_whenMatchesOnDifferentCases_thenCorrect() {
    int matches = runTest("(?i)dog", "This is a Dog");
    assertTrue(matches > 0);
}

Pattern.COMMENTS

Java API 允許我們使用 # 在正則表達(dá)式中包含注釋。這有助于為可能對其他程序員不太明顯的復(fù)雜正則表達(dá)式添加文檔說明。

注釋標(biāo)志使匹配器忽略正則表達(dá)式中的任何空格或注釋,并只考慮模式。在默認(rèn)匹配模式下,以下測試將失?。?/p>

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

這是因為匹配器將在輸入文本中查找整個正則表達(dá)式,包括空格和 # 字符。但是,當(dāng)我們使用標(biāo)志時,它會忽略多余的空格,并將 # 開頭的所有文本視為注釋,忽略每行的注釋:

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

還有一個用于此的替代嵌入標(biāo)志表達(dá)式:

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

Pattern.DOTALL

默認(rèn)情況下,當(dāng)我們在正則表達(dá)式中使用點“.”表達(dá)式時,我們會匹配輸入字符串中的每個字符,直到遇到換行符。

使用此標(biāo)志,匹配將包含行終止符。通過以下示例我們將更好地理解這一點。由于我們希望對匹配的字符串進(jìn)行斷言,我們將使用 matchergroup 方法,它返回上一個匹配項。

首先,讓我們看看默認(rèn)行為:

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

如我們所見,只有在行終止符之前的輸入部分被匹配。

現(xiàn)在在 dotall 模式中,整個文本(包括行終止符)將被匹配:

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

我們還可以使用嵌入標(biāo)志表達(dá)式啟用 dotall 模式:

@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

在此模式下,匹配器不會給任何元字符、轉(zhuǎn)義字符或正則表達(dá)式語法賦予特殊含義。如果沒有此標(biāo)志,匹配器將匹配以下正則表達(dá)式與任意輸入字符串:

@Test
public void givenRegex_whenMatchesWithoutLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text");
    assertTrue(matches > 0);
}

這是我們在所有示例中看到的默認(rèn)行為。但是,使用此標(biāo)志后,我們將找不到匹配,因為匹配器將查找 (.*) 而不是解釋它:

@Test
public void givenRegex_whenMatchFailsWithLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text", Pattern.LITERAL);
    assertFalse(matches > 0);
}

現(xiàn)在如果我們添加所需的字符串,測試將通過:

@Test
public void givenRegex_whenMatchesWithLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text(.*)", Pattern.LITERAL);
    assertTrue(matches > 0);
}

沒有用于啟用文字解析的嵌入標(biāo)志字符。

Pattern.MULTILINE

默認(rèn)情況下,^$ 元字符分別絕對匹配整個輸入字符串的開始和結(jié)束,匹配器忽略任何行終止符:

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

通過啟用此標(biāo)志,匹配器將匹配每一行,而不僅僅是整個輸入文本:

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

我們還可以使用嵌入表達(dá)式來啟用多行模式:

@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. Matcher 類方法

在本節(jié)中,我們將學(xué)習(xí) Matcher 類中的一些有用方法。為了清晰起見,我們將根據(jù)功能對其進(jìn)行分類。

12.1 索引方法

索引方法提供了有用的索引值,準(zhǔn)確顯示在輸入字符串中找到匹配的位置。在下面的測試中,我們將確認(rèn)輸入字符串中 "dog" 的匹配起始和結(jié)束索引:


@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 研究方法

研究方法遍歷輸入字符串,并返回一個布爾值,指示是否找到了模式。常用的方法有 matches 和 lookingAt。

matches 和 lookingAt 方法都嘗試將輸入序列與模式進(jìn)行匹配。區(qū)別在于 matches 要求整個輸入序列完全匹配,而 lookingAt 不需要。

這兩個方法都從輸入字符串的開頭開始匹配:

@Test
public void whenStudyMethodsWork_thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dogs are friendly");
    assertTrue(matcher.lookingAt());
    assertFalse(matcher.matches());
}

在這種情況下,matches 方法將返回 true:

@Test
public void whenMatchesStudyMethodWorks_thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dog");
    assertTrue(matcher.matches());
}

12.3 替換方法

替換方法用于替換輸入字符串中的文本。常見的方法有 replaceFirst 和 replaceAll。

replaceFirst 和 replaceAll 方法用于替換與給定正則表達(dá)式匹配的文本。顧名思義,replaceFirst 只替換第一次出現(xiàn)的匹配項,replaceAll 替換所有出現(xiàn)的匹配項:

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

替換所有出現(xiàn)的匹配項:

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

replaceAll 方法允許我們用相同的替換文本替換所有匹配項。如果我們想基于具體情況替換匹配項,則需要使用一種令牌替換技術(shù)。

13. 結(jié)論

在本文中,我們學(xué)習(xí)了如何在 Java 中使用正則表達(dá)式。我們還探討了 java.util.regex 包中的最重要功能。若你想提升Java技能,可關(guān)注我們的Java培訓(xùn)課程。