【问题标题】:Rationale for Matcher throwing IllegalStateException when no 'matching' method is called当没有调用“匹配”方法时,Matcher 抛出 IllegalStateException 的理由
【发布时间】:2012-10-06 08:44:38
【问题描述】:

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) 处添加对matches() 的调用会将Matcher 设置为调用group() 的正确状态。

问题,可能没有建设性

为什么这个 API 是这样设计的?当Matcher 是用Patter.matcher(String) 构建时,为什么不自动 匹配?

【问题讨论】:

  • 有趣。即使在问题、答案和赏金解决几周后,人们仍然认为这个问题值得一票否决。
  • 我发现这是一个糟糕的 API 设计 - 有点相当于在构建后需要一种“initialize()”。在某些合法情况下,您可以知道给定的 Matcher 已经匹配给定的字符串。
  • @Ben 我在框架中见过的最糟糕的 API 设计!非常不直观,带有无用的无用错误消息:-(

标签: java regex illegalstateexception


【解决方案1】:

实际上,您误解了文档。第二次看你引用的陈述:-

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

如果没有找到匹配,匹配器可能会在访问 matcher.group() 时抛出 IllegalStateException

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

 - 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 检查将无法做到这一点。为此,您必须将匹配器向前移动以匹配下一个字符串。因此,Matcher 类中定义了各种方法来达到目的。 matcher.find() 方法匹配字符串,直到找到所有匹配项。

还有其他方法,即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 方法尝试匹配整个输入序列 反对模式。

  • lookingAt 方法尝试匹配输入序列,开始 一开始,反模式。

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

很遗憾,我找不到任何其他官方来源,明确说明为什么如何这个问题。

【讨论】:

  • 是的,这就是文档所说的。我知道。我的(非建设性的)问题是:为什么这个 API 是这样设计的?
  • @Tichodroma 这是一个很难给出客观答案的问题。设计东西总是有多种方法。只有发明这个的人才能解释他或她选择这个解决方案的原因。
  • @Tichodroma 这是因为,直到你 match 一个带有特定字符串的模式。它不会包含任何匹配的数据。很简单:-You cannot access a string that has not been matched yet.
  • @Tichodroma 基本上,matcher.group() 尝试返回在当前字符串中找到的match。但是你必须initiate匹配操作。这就是matcher.matches() 所做的。
  • @Tichodroma。你明白了吗?或者你仍然很困惑。您无法获得与模式不匹配的字符串。就这么简单。
【解决方案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);

PatternString 返回一个新的Matcher 对象——一个尚未读取字符串的对象。 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
    

    请参阅PatternJavaDoc 中“组和捕获”中“组名称”部分

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

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

【讨论】:

    【解决方案3】:

    如果匹配器会自动匹配输入字符串,如果您希望找到模式,那将是浪费精力。 p>

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

    【讨论】:

      【解决方案4】:

      这是预期并记录在案的。

      原因是.matches() 返回一个布尔值,指示是否存在匹配项。如果有匹配,那么您可以有意义地调用.group(...)。否则,如果没有匹配,则调用.group(...) 毫无意义。因此,您不应在调用matches() 之前调用.group(...)

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

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

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        我承认重复使用 IllegalStateException 可能不会导致错误情况的最佳描述。但是,如果我们将IllegalStateException 重命名/子类化为NoSuccessfulMatchException,应该能够理解当前设计如何强制查询一致性并鼓励用户使用具有在询问时定义的语义的查询.

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

        【讨论】:

          【解决方案6】:

          你需要检查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() 来获取有关匹配项的信息没有多大意义。

          【讨论】:

            猜你喜欢
            • 2011-07-09
            • 1970-01-01
            • 1970-01-01
            • 2017-06-10
            • 1970-01-01
            • 2011-08-06
            • 1970-01-01
            相关资源
            最近更新 更多