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

TL; DR

Matcher API的devise决定是什么?

背景

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

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

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

问题可能不是build设性的

为什么这个API是这样devise的? 为什么MatcherPatter.matcher(String)一起构build时不会自动匹配?

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

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

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

所以,你需要使用下面的testing,来实际启动匹配过程:

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

下面的代码: –

 Matcher matcher = pattern.matcher(); 

只需创build一个matcher实例。 这实际上不会匹配一个string。 即使有一个成功的比赛。 所以,你需要检查以下条件,以检查成功的匹配: –

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

如果if中的条件返回false ,则意味着什么都没有匹配。 所以,如果你在不检查这个条件的情况下使用matcher.group() ,那么如果找不到匹配,你将会得到IllegalStateException


假设,如果Matcher是按照你所说的方式devise的,那么你将不得不做一个null检查来检查是否find了一个匹配,来调用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()); } 

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

还有其他方法,以不同的方式matchstring,这取决于你如何匹配。 所以它最终在Matcher类上对string进行matchingPattern类只是创build一个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()); } 

因此,如果在string中find了4个匹配项,则第一种方法是仅打印第一个匹配项,而第二种方法将打印所有匹配项,方法是将matcher向前移动以匹配下一个匹配项。

我希望能够说清楚。

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

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

  • matches方法尝试将整个input序列与模式匹配。

  • lookAt方法尝试从input序列开始匹配模式。

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

不幸的是,我一直没有find任何其他官方消息来源,明确说明这个问题的原因和方法。

我的答案与罗希特·耆那教的非常相似,但包括了为什么 “额外”步骤是必要的一些原因。

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对象 – 一个尚未读取String的对象。 Matcher实际上只是状态机的状态,状态机是Pattern

匹配可以通过使用以下API通过匹配进程来使状态机运行:

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

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

这种方法的好处

为什么会有人想要通过parsing?

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

     Pattern p = new Pattern("([az]=([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中的“组名称”部分

  2. 开发人员可以使用RE作为词法分析器 ,开发人员可以将lexed标记绑定到parsing器 。 在实践中,这将适用于简单的领域语言,但正则expression式可能不是完整的计算机语言的方式。 编辑这部分与前面的原因有关,但是创build处理文本的分析树比首先查找所有input要经常更容易和更高效。
  3. (对于勇敢者)你可以debuggingRE,找出哪个子序列不匹配(或不正确的匹配)。

然而,在大多数情况下,你不需要通过匹配步进状态机,所以有一个方便的方法( matches )来运行模式匹配到完成。

如果一个匹配器会自动匹配input的string,那么如果你希望find这个模式的话,这将是浪费精力

匹配器可以用来检查模式是否matches()inputstring,它可以用来在inputstring中find()模式(甚至可以重复查找所有匹配的子string)。 在你调用这两种方法之一之前,匹配器不知道你想要执行什么testing,所以它不能给你任何匹配的组。 即使您调用其中一种方法,调用也可能失败 – 找不到模式 – 在这种情况下,调用group必须失败。

这是预期和logging。

原因是.matches()返回一个指示是否匹配的布尔值。 如果有匹配,那么你可以打电话给.group(...)有意义。 否则,如果没有匹配,调用.group(...)是没有意义的。 因此,在调用matches()之前,不应该允许调用.group(...) matches()

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

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

我的猜测是,devise决定是基于查询具有清晰明确的语义,而不会将存在与匹配属性相混淆。

考虑一下:如果匹配器没有成功地匹配某些东西,你会期望Matcher查询返回什么结果?

我们先考虑一下group() 。 如果我们没有成功地匹配一些东西,匹配器不应该返回空string,因为它没有匹配空string。 在这一点上我们可以返回null

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

鉴于这一切,用户仍然需要检查每个查询的返回值,以validation是否发生匹配。 或者,你可以检查它是否成功匹配,如果成功,查询语义都有明确的含义。 如果不是,则无论查询哪个angular度,用户都会得到一致的行为。

我将授予重新使用IllegalStateException可能没有导致错误情况的最佳描述。 但是,如果我们要将IllegalStateException重命名为子类或将其子类NoSuccessfulMatchException ,则应该能够理解当前devise如何强制执行查询一致性,并鼓励用户使用具有已知在请求时定义的语义的查询。

TL; DR :询问活体死亡的具体原因的价值是什么?

你需要检查matcher.matches()的返回值。 当find匹配时它将返回true ,否则返回false

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

如果matcher.matches()没有find匹配项并且调用matcher.group(...) ,您仍然会得到一个IllegalStateException matcher.group(...) 。 这正是文档所说的:

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

matcher.match()返回false ,没有find成功的匹配,并且通过调用例如group()来获取关于匹配的信息没有多大意义。