1. 概述

有时我们可能会遇到将字符串与正则表达式匹配的困难。例如,我们可能不知道我们想要精确匹配什么,但我们可以知道它的周围环境,例如它之前的内容或之后缺少的内容。在这些情况下,我们可以使用环视断言。这些表达式称为断言,因为它们仅指示某些内容是否匹配,但不包含在结果中。

在本教程中,我们将了解如何使用四种类型的正则表达式环视断言。

2. 正向前瞻

假设我们要分析 java 文件的导入。首先,我们通过检查 import 关键字后面是否有 static 关键字来查找 静态的 import 语句。

让我们在表达式中使用带有 (?=criteria) 语法的正向先行断言来匹配主表达式 导入 后的 静态 字符组:

Pattern pattern = Pattern.compile("import (?=static).+");

Matcher matcher = pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());

assertFalse(pattern.matcher("import java.util.regex.Matcher;").find());

3. 负向前瞻

接下来,让我们执行与上一个示例相反的操作,并查找不是 static 的 import 语句。让我们通过检查 static 关键字是否不跟在 import 关键字后面来完成此操作。

让我们在表达式中使用带有 (?!criteria) 语法的否定先行断言,以确保在主表达式 导入静态 字符组无法匹配:

Pattern pattern = Pattern.compile("import (?!static).+");

Matcher matcher = pattern.matcher("import java.util.regex.Matcher;");
assertTrue(matcher.find());
assertEquals("import java.util.regex.Matcher;", matcher.group());

assertFalse(pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());

4. Java 中 Lookbehind 的局限性

在 Java 8 之前,我们可能会遇到这样的限制:在后向断言中不允许使用未绑定的量词,例如 +* 。也就是说,例如,以下断言在 Java 8 之前都会抛出 PatternSyntaxException

  • (?<!fo+)bar ,如果 fo 前面有一个或多个 o 字符,我们不想匹配 bar
  • (?<!fo*)bar ,如果 bar 前面有一个 f 字符,后跟零个或多个 o 字符,我们不想匹配 bar
  • (?<!fo{2,})bar ,如果 foo 前面有两个或多个 o 字符,我们不想匹配 bar

作为解决方法,我们可以使用具有指定上限的大括号量词,例如 (?<!fo{2,4})bar ,其中我们将 f 字符后面的 o 字符数最大化为 4。

从 Java 9 开始,我们可以在lookbehinds中使用未绑定的量词。但是,由于正则表达式实现的内存消耗,仍然建议仅在lookbehinds中使用具有合理上限的量词,例如 (?<!fo{2,20})bar 而不是 (?<!fo{ 2,2000}) 酒吧

5. 积极的回顾

假设我们想在分析中区分 JUnit 4 和 JUnit 5 导入。首先,我们检查 assertEquals 方法的导入语句是否来自 jupiter 包。

让我们在表达式中使用带有 (?<=criteria) 语法的正后向断言来匹配主表达式 .* assertEquals 之前的字符组 jupiter

Pattern pattern = Pattern.compile(".*(?<=jupiter).*assertEquals;");

Matcher matcher = pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());

assertFalse(pattern.matcher("import static org.junit.Assert.assertEquals;").find());

6. 消极的回顾

接下来,让我们执行与上一个示例相反的操作,查找不是来自 jupiter 包的导入语句。

为此,我们在表达式中使用带有 (?<!criteria) 语法的负后向断言,以确保字符组 jupiter.{0,30} 无法在主表达式 assertEquals 之前匹配:

Pattern pattern = Pattern.compile(".*(?<!jupiter.{0,30})assertEquals;");

Matcher matcher = pattern.matcher("import static org.junit.Assert.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.Assert.assertEquals;", matcher.group());

assertFalse(pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());

七、结论

在本文中,我们了解了如何使用四种类型的正则表达式环视来解决使用正则表达式匹配字符串的一些困难情况。

与往常一样,本文的源代码可在 GitHub 上获取。


« 上一篇: 学习Spring Boot
» 下一篇: Java入门教程