Matcher 在没有调用“匹配”方法时抛出 IllegalStateException 的理由

2022-09-01 10:40:09

TL;DR

Matcher的API背后的设计决策是什么?

背景

Matcher有一种我没有预料到的行为,我找不到一个很好的理由。API 文档说:

创建后,可以使用匹配器执行三种不同类型的匹配操作: [...]这些方法中的每一个都返回一个布尔值,指示成功或失败。可以通过查询匹配器的状态来获取有关成功匹配的详细信息。

API 文档进一步说明的是:

匹配器的显式状态最初是未定义的;在成功匹配之前尝试查询它的任何部分都将导致抛出非法状态异常。

String s = "foo=23,bar=42";
Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");
Matcher matcher = p.matcher(s);
System.out.println(matcher.group("foo")); // (1)
System.out.println(matcher.group("bar"));

此代码抛出一个

java.lang.IllegalStateException: No match found

在。要解决此问题,有必要调用 或其他方法,使 进入允许 的状态。以下工作原理:(1)matches()Matchergroup()

String s = "foo=23,bar=42";
Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");
Matcher matcher = p.matcher(s);
matcher.matches(); // (2)
System.out.println(matcher.group("foo"));
System.out.println(matcher.group("bar"));

将对 matches() at 的调用设置为要调用 的正确状态。(2)Matchergroup()

问题,可能没有建设性

为什么这个 API 是这样设计的?为什么不在 构建时使用 自动匹配 ?MatcherPatter.matcher(String)


答案 1

实际上,您误解了文档。再看看你引用的声明: -

在成功匹配之前尝试查询它的任何部分都将导致抛出非法状态异常。

如果没有找到匹配项,匹配者可能会投掷访问。IllegalStateExceptionmatcher.group()

所以,你需要使用以下测试,来实际启动匹配过程: -

 - matcher.matches() //Or
 - matcher.find()

以下代码: -

Matcher matcher = pattern.matcher();  

只需创建一个实例。这实际上不会与字符串匹配。即使有一场成功的比赛。因此,您需要检查以下条件,以检查是否成功匹配: -matcher

if (matcher.matches()) {
    // Then use `matcher.group()`
}

如果返回中的条件,则意味着没有匹配。因此,如果您在不检查此条件的情况下使用,则如果找不到匹配项,您将获得。iffalsematcher.group()IllegalStateException


假设,如果按照您所说的方式设计,那么您必须进行检查以检查是否找到匹配项,并调用,如下所示: -Matchernullmatcher.group()

你认为应该这样做的方式:-

// Suppose this returned the matched string
Matcher matcher = pattern.matcher(s);  

// Need to check whether there was actually a match
if (matcher != null) {  // Prints only the first match

    System.out.println(matcher.group());
}

但是,如果,如果您想打印任何进一步的匹配项,因为一个模式可以在字符串中多次匹配,因此,应该有一种方法可以告诉匹配者找到下一个匹配项。但是支票将无法做到这一点。为此,您必须向前移动匹配器以匹配下一个字符串。因此,在类中定义了各种方法来达到目的。该方法与 String 匹配,直到找到所有匹配项。nullMatchermatcher.find()

还有其他方法,以不同的方式字符串,这取决于您想要如何匹配。所以它最终在类上做对字符串。 类只是创建一个匹配。如果是模式,那么必须有某种方法来定义各种方法,就像以不同的方式一样。所以,需要上课。matchMatchermatchingPatternpatternPattern.matcher()matchmatchmatchingMatcher

所以,它实际上是这样: -

Matcher matcher = pattern.matcher(s);

   // Finds all the matches until found by moving the `matcher` forward
while(matcher.find()) {
    System.out.println(matcher.group());
}

因此,如果在字符串中找到4个匹配项,则第一种方法将仅打印第一个匹配项,而第二种方法将打印所有匹配项,方法是向前移动以匹配下一个模式。matcher

我希望这能说明问题。

Matcher类的文档描述了它提供的三种方法的使用,它说: -

匹配器是通过调用模式的匹配器方法从模式创建的。创建匹配器后,可以使用匹配器执行三种不同类型的匹配操作:

  • match 方法尝试将整个输入序列与模式进行匹配。

  • lookingAt 方法尝试将输入序列(从头开始)与模式进行匹配。

  • find 方法扫描输入序列,查找与模式匹配的下一个子序列。

不幸我无法找到任何其他官方消息来源,明确说明为什么和如何解决这个问题。


答案 2

我的答案与Rohit Jain的非常相似,但包括一些原因,为什么“额外”步骤是必要的。

java.util.regex 实现

该行:

Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");

导致分配一个新的 Pattern 对象,并在内部存储一个表示 RE 的结构 - 诸如字符、组、序列、贪婪与非贪婪、重复等信息的选择。

这种模式是无状态和不可变的,因此可以重用,多属性且优化良好。

线路:

String s = "foo=23,bar=42";
Matcher matcher = p.matcher(s);

返回 and - 一个尚未读取字符串的新对象。 实际上只是一个状态机的状态,其中状态机是 .MatcherPatternStringMatcherPattern

可以通过使用以下 API 逐步执行匹配过程来运行匹配:

  • lookingAt():尝试将输入序列(从头开始)与模式进行匹配
  • find():扫描输入序列,查找与模式匹配的下一个子序列。

在这两种情况下,都可以使用 、 和 方法读取中间状态。start()end()group()

这种方法的好处

为什么有人想通过解析来执行?

  1. 从量化大于 1 的组(即重复并最终匹配多次的组)中获取值。例如,在下面解析变量赋值的简单RE中:

    Pattern p = new Pattern("([a-z]=([0-9]+);)+");
    Matcher m = p.matcher("a=1;b=2;x=3;");
    m.matches();
    System.out.println(m.group(2)); // Only matches value for x ('3') - not the other values
    

    请参阅“组和捕获”JavaDoc on Pattern 中的“组名”部分

  2. 开发人员可以使用 RE 作为词法分析器,开发人员可以将词法标记绑定到解析器。在实践中,这适用于简单的域语言,但正则表达式可能不是成熟的计算机语言的方法。编辑这与前面的原因部分相关,但创建处理文本的解析树通常比首先对所有输入进行词法处理更容易和更有效。
  3. (对于勇敢的人)您可以调试REE并找出哪个子序列无法匹配(或不正确匹配)。

但是,在大多数情况下,您不需要通过匹配来单步执行状态机,因此有一种方便的方法()可以运行模式匹配以完成。matches