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
包由三個類組成:Pattern
、Matcher
和 PatternSyntaxException
:
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);
}
由于我們將運(yùn)行更多測試,可以將找到匹配次數(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 支持幾個元字符,最簡單的就是點(diǎn)“.”,它匹配任何字符:
@Test
public void givenText_whenMatchesWithDotMetach_thenCorrect() {
int matches = runTest(".", "foo");
assertTrue(matches > 0);
}
讓我們考慮前面的示例,其中正則表達(dá)式 foo
匹配了文本 foo
以及 foofoo
,兩次。如果我們在正則表達(dá)式中使用點(diǎn)元字符,在第二種情況下我們不會得到兩次匹配:
@Test
public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() {
int matches = runTest("foo.", "foofoo");
assertEquals(matches, 1);
}
注意正則表達(dá)式中 foo
后面的點(diǎn)。匹配器匹配每個 foo
之后的文本,因為最后的點(diǎn)部分表示任何字符。所以在找到第一個 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 交叉點(diǎn)類
類似于聯(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,使用反向引用告訴匹配器我們要匹配文本的另一部分。這樣,輸入不會有兩個單獨(dú)的匹配:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect2() {
int matches = runTest("(\\\\d\\\\d)", "1212");
assertEquals(matches, 2);
}
我們可以獲得一次匹配,但通過反向引用將相同的正則表達(dá)式匹配擴(kuò)展到整個輸入的長度:
@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ù)合代碼點(diǎn)是 u00E9
。然而,Unicode 也有其組件字符的獨(dú)立代碼點(diǎn):e
(u0065
)和重音符號 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á)式中使用點(diǎn)“.”表達(dá)式時,我們會匹配輸入字符串中的每個字符,直到遇到換行符。
使用此標(biāo)志,匹配將包含行終止符。通過以下示例我們將更好地理解這一點(diǎn)。由于我們希望對匹配的字符串進(jìn)行斷言,我們將使用 matcher
的 group
方法,它返回上一個匹配項。
首先,讓我們看看默認(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)課程。