Predefined字符类

Java正则表达式API也接受预定义的字符类。上面的一些字符类可以用更短的形式表示,尽管这会降低代码的直观性。这个正则表达式的Java版本的一个特殊方面是转义字符。

正如我们将看到的,大多数字符都以反斜杠开头,这在Java中有特殊的意义。对于要由模式类编译的这些,必须转义前导反斜杠,即 .\d 变为 \\d 。

匹配的数字,相当于 [0-9] :


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


匹配非数字,相当于 [^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);
}


匹配一个单词字符,相当于 [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);
}


Quantifiers

Java正则表达式API还允许我们使用Quantifiers。通过指定匹配的出现次数,我们可以进一步调整匹配的行为。

要匹配零次或一次文本,我们使用  量词:


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


或者,我们可以使用大括号语法,Java regex API也支持这种语法:


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


本例介绍了零长度匹配的概念。碰巧的是,如果一个量词的匹配阈值为零,它总是匹配文本中的所有内容,包括每个输入末尾的一个空字符串。这意味着即使输入为空,它也将返回一个零长度匹配。

这就解释了为什么在上面的示例中,尽管字符串长度为2,但我们仍得到3个匹配项。第三个匹配项是长度为零的空字符串。

为了匹配零次或无限次的文本,我们使用 * 量词,它与?:


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


差异量词为+,匹配阈值为1。如果所需的字符串根本不出现,则将不存在匹配项,甚至不存在长度为零的字符串:


@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和其他语言中一样,大括号语法可用于多次匹配给定文本:


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


在上面的例子中,我们得到了两个匹配项,因为只有当a在一行中出现三次时,才会出现匹配项。但是,在下一次测试中,我们不会得到匹配,因为文本在一行中只出现两次:


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


当我们在大括号中使用范围时,匹配将是贪婪的,从范围的高端匹配:


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


我们已经指定了至少两次但不超过三次,所以我们得到一个匹配,匹配者看到一个 aaa 和一个无法匹配的 a 。

然而,API允许我们指定一种懒惰或不情愿的方法,以便匹配器可以从范围的低端开始,在这种情况下,匹配两个匹配项aa和aa:


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


Capturing Groups

API还允许我们通过Capturing Groups将多个角色视为一个单元。

它会将数字附加到Capturing Groups,并允许使用这些数字进行反向引用。

在本节中,我们将看到一些关于如何在Java正则表达式API中使用Capturing Groups的示例。

让我们使用一个仅当输入文本包含两个相邻数字时才匹配的Capturing Groups:


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


上面匹配的数字是1,使用 back 引用告诉匹配者我们想要匹配文本匹配部分的另一个匹配项。这样做,而不是:


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


如果输入有两个单独的匹配项,我们可以有一个匹配项,但使用反向引用传播相同的正则表达式匹配项以跨越输入的整个长度:


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


我们必须重复正则表达式,而无需反向引用,才能获得相同的结果:


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


类似地,对于任何其他重复次数,反向引用可以使匹配者将输入视为单个匹配:


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


但如果你甚至改变了最后一个数字,匹配就会失败:


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


重要的是不要忘记转义反斜杠,这在Java语法中至关重要。