当没有调用“匹配”方法时,Matcher抛出IllegalStateException的原理

时间:2012-10-16 09:26:15

标签: java regex illegalstateexception

TL; DR

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

背景

Matcher有一种我没想到的行为,我找不到合理的理由。 API文档说:

  

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

API文档进一步说明了:

  

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

示例

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()或其他方法将Matcher置于允许group()的状态。以下作品:

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"));

将来自(2)的{​​{1}}添加到Matcher,将group()设置为正确状态,以便Matcher

问题,可能不具有建设性

为什么这个API是这样设计的?在使用Patter.matcher(String)构建{{1}}时,为什么自动匹配?

6 个答案:

答案 0 :(得分:29)

实际上,你误解了文档。再看看你引用的陈述: -

  

在成功匹配之前尝试查询它的任何部分将导致   抛出IllegalStateException。

如果未找到匹配项,匹配器可能会IllegalStateException访问matcher.group()

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

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

以下代码: -

Matcher matcher = pattern.matcher();  

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

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

如果if中的条件返回false,则表示没有匹配。因此,如果您在未检查此条件的情况下使用matcher.group(),则在未找到匹配项时将获得IllegalStateException


假设,如果按照您的说法设计Matcher,那么您必须进行null检查以检查是否找到了匹配项,以致matcher.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());
}

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

还有其他方法,match字符串以不同的方式,取决于你想要匹配的方式。因此它最终在Matcher类上对字符串执行matchingPattern类只会创建一个pattern来匹配。如果Pattern.matcher()符合match模式,则必须有某种方式来定义match的各种方式,因为matching可以采用不同的方式。因此,需要Matcher类。

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

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类的文档描述了它提供的三种方法的使用,其中说: -

  

通过调用模式的匹配器从模式创建匹配器   方法。一旦创建,匹配器可用于执行三种不同的   各种匹配操作:

     
      
  • matches方法尝试匹配整个输入序列   反对模式。

  •   
  • lookupAt方法尝试匹配输入序列,start   在开头,反对模式。

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

  •   

不幸的是,我找不到任何其他官方消息来源,明确说明为什么 这个问题。

答案 1 :(得分:6)

我的回答与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);

MatcherPattern返回一个新的String对象 - 一个尚未读取字符串的对象。 Matcher实际上只是状态机的状态,状态机是Pattern

可以通过使用以下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
    

    请参阅Pattern

    上的“群组并捕获”JavaDoc中的“群组名称”部分
  2. 开发人员可以将RE用作lexer,开发人员可以将lexed令牌绑定到parser。实际上,这适用于简单的域语言,但正则表达式可能不适合使用成熟的计算机语言。 编辑这部分与之前的原因相关,但创建处理文本的解析树通常比首先输入所有输入更容易,更有效。
  3. (对于勇敢的人),您可以调试RE并找出哪个子序列无法匹配(或不正确匹配)。
  4. 但是,在大多数情况下,您不需要通过匹配来执行状态机,因此有一种方便的方法(matches)可以运行模式匹配以完成。

答案 2 :(得分:4)

如果匹配器会自动匹配输入字符串,那么浪费精力,以防您希望找到模式。

匹配器可用于检查模式matches()是否为输入字符串,并且可用于find()输入字符串中的模式(甚至重复查找所有匹配的子字符串)。在您调用这两种方法之一之前,匹配器不知道您要执行的测试,因此它无法为您提供任何匹配的组。即使您确实调用了其中一种方法,调用也可能会失败 - 找不到模式 - 在这种情况下,对group的调用也必须失败。

答案 3 :(得分:2)

这是预期和记录的。

原因是.matches()返回一个布尔值,表明是否匹配。如果匹配,则可以有意义地致电.group(...)。否则,如果没有匹配,则调用.group(...)毫无意义。因此,在致电.group(...)之前,不应该致电matches()

使用匹配器的正确方法如下:

Matcher m = p.matcher(s);
if (m.matches()) {
  ...println(matcher.group("foo"));
  ...
}

答案 4 :(得分:2)

我的猜测是设计决策是基于具有明确,定义明确的语义的查询,这些语义不会将存在与匹配属性混为一谈。

考虑一下:如果匹配器没有成功匹配某些内容,您希望Matcher查询返回什么?

我们首先考虑group()。如果我们没有成功匹配某些东西,Matcher不应该返回空字符串,因为它与空字符串不匹配。我们此时可以返回null

好的,现在让我们考虑一下start()end()。每次返回int。在这种情况下,int值有效吗?当然没有正数。什么负数是合适的? -1

鉴于这一切,用户仍然必须检查每个查询的返回值,以验证是否发生匹配。或者,您可以检查它是否成功匹配,如果成功,查询语义都具有明确定义的含义。如果不是,则无论查询哪个角度,用户都会获得一致的行为。

我会批准重新使用IllegalStateException可能无法最好地描述错误情况。但是如果我们要将IllegalStateException重命名为NoSuccessfulMatchException,那么应该能够理解当前设计如何强制执行查询一致性并鼓励用户使用具有已知定义语义的查询。问的时候。

TL; DR :询问生物体死亡的具体原因有什么价值?

答案 5 :(得分:1)

您需要检查matcher.matches()的返回值。找到匹配项时会返回true,否则会返回false

if (matcher.matches()) {
    System.out.println(matcher.group("foo"));
    System.out.println(matcher.group("bar"));
}

如果matcher.matches()找不到匹配项并且您致电matcher.group(...),您仍会获得IllegalStateException。这正是文档所说的:

  

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

matcher.match()返回false时,找不到成功匹配,通过调用group()来获取有关匹配的信息没有多大意义。