在本文中,我们将讨论java正则表达式
API,以及如何在Java编程语言中使用正则表达式。

在正则表达式的世界中,有许多不同的风格可供选择,比如grep、Perl、Python、PHP、awk等等。

这意味着在一种编程语言中工作的正则表达式可能在另一种编程语言中不工作。Java中的正则表达式语法与Perl中的最相似。

要在Java中使用正则表达式,我们不需要任何特殊设置。JDK包含一个特殊的java包java.util.regex完全致力于regex。我们只需要将其导入到我们的代码中。

此外,java.lang.String类还具有我们在代码中常用的内置正则表达式支持。

Java正则表达式包

 java.util.regex包由三个类组成:PatternMatcherPatternSyntaxException

  • Pattern模式对象是一个已编译的正则表达式。Pattern类不提供公共构造函数。要创建一个模式,我们必须首先调用它的一个公共静态编译方法,然后该方法将返回一个模式对象。这些方法接受正则表达式作为第一个参数。
  • Matcher对象解释模式并对输入字符串执行匹配操作。它也没有定义公共构造函数。我们通过调用模式对象上的Matcher方法来获得Matcher对象。
  • PatternSyntaxException对象是一个未经检查的异常,它指示正则表达式模式中的语法错误。

我们必须首先了解正则表达式是如何在Java中构造的。

如果你已经从不同的环境中熟悉了正则表达式,你可能会发现某些差异,但它们是最小的。

简单的例子

让我们从正则表达式的最简单用例开始。如前所述,当正则表达式应用于字符串时,它可能会匹配零次或多次。

java支持的最基本的模式匹配形式。java.util.regex正则表达式API是字符串文本的匹配。例如,如果正则表达式为foo,输入字符串为foo,则匹配将成功,因为字符串相同:

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

我们首先通过调用其静态编译方法并向其传递我们想要使用的模式来创建一个Pattern对象。

然后我们创建一个Matcher对象,调用Pattern对象的Matcher方法,并将要检查匹配的文本传递给它。

之后,我们在Matcher对象中调用find方法。

find方法在输入文本中不断前进,并为每个匹配返回true,因此我们也可以使用它来查找匹配计数:

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

由于我们将运行更多的测试,我们可以抽象出在一个名为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;
}

当我们得到0个匹配项时,测试应该失败,否则应该通过。

Meta Characters元字符

元字符会影响模式匹配的方式,从而为搜索模式添加逻辑。JavaAPI支持多个Meta Characters,最简单的是“.”匹配任何字符:

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

考虑到前面的例子,其中regex-foo匹配文本foo和foo两次。如果我们在正则表达式中使用点元字符,那么在第二种情况下,我们不会得到两个匹配:

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

注意正则表达式中foo后面的点。匹配器匹配前面有foo的每个文本,因为最后一个点部分表示后面的任何字符。因此,在找到第一个foo之后,其余的被视为任何角色。这就是为什么只有一场比赛。

该API支持其他几个元字符<([{\^-=$!|]})?*+.>我们将在本文中进一步探讨。

Character类

浏览官方模式类规范,我们将发现受支持的正则表达式构造的摘要。在Character类下,我们有大约6个结构。

OR

构造为[abc]。集合中的任何元素都是匹配的:

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

如果它们都出现在文本中,则每一个单独匹配,不考虑顺序:

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

它们也可以作为字符串的一部分进行替换。在下面的示例中,当我们通过将第一个字母与集合中的每个元素交替来创建不同的单词时,它们都是匹配的:

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

NOR

通过添加插入符号作为第一个元素来否定上述集合:

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

Range类

我们可以定义一个类,该类使用连字符(-)指定匹配文本应该落在的范围内,同样,我们也可以否定一个范围。

匹配大写字母:

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

匹配范围:

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

匹配另外的数字范围:

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

Union类

union字符类是两个或多个字符类组合的结果:

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

上述测试将只匹配9个整数中的6个,因为并集跳过4、5和6。

Intersection类

与union类类似,该类是在两个或多个集合之间拾取公共元素的结果。要应用交叉点,我们使用&&:

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

我们得到4个匹配,因为两个集合的交集只有4个元素。

Subtraction类

我们可以使用减法对一个或多个字符类求反,例如匹配一组奇数十进制数:

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