多行模式下正则表达式无法匹配空字符串(Java)

16

我刚刚观察到了这种行为;

Pattern p1 = Pattern.compile("^$");
Matcher m1 = p1.matcher("");
System.out.println(m1.matches()); /* true */

Pattern p2 = Pattern.compile("^$", Pattern.MULTILINE);
Matcher m2 = p2.matcher("");
System.out.println(m2.matches()); /* false */

我觉得最后的陈述是不合适的。这就是文档所说的:

默认情况下,正则表达式^和$忽略行终止符,并且仅匹配整个输入序列的开头和结尾。如果激活MULTILINE模式,则^匹配输入的开头和除输入结束符外的任何行终止符之后。在MULTILINE模式下,$匹配行终止符或输入序列的结尾之前。 http://docs.oracle.com/javase/1.4.2...

从我的理解中,它应该匹配成功。接下来的内容更加混乱了;

Pattern p3 = Pattern.compile("^test$");
Matcher m3 = p3.matcher("test");
System.out.println(m3.matches()); /* true */

Pattern p4 = Pattern.compile("^test$", Pattern.MULTILINE);
Matcher m4 = p4.matcher("test");
System.out.println(m4.matches()); /* true */

那么这是什么?我如何理解这个?希望有人能够给我一些启示,真的会很感激。


1
这是Java SE 6 (MacOS X 默认)。 - Wietse Venema
刚刚在OpenJDK(IcedTea6 1.9.10)上尝试了一下,结果发现相同的奇怪行为仍然存在。 - Davy Landman
3个回答

9
如果激活了MULTILINE模式,则^会在输入开头和任何行终止符(除了输入的末尾)之后匹配。因此,在多行模式下,由于您在输入的末尾,^无法匹配。虽然令人惊讶甚至让人恶心,但这是根据其文档的规定。

这个“除了在输入的结尾”只是指“在任何行终止符之后”。由于我们没有行终止符,所以我们处于输入的开头,因此应该匹配。 - stema
/^$/m, /^$/, /\A\Z/m, /\A\Z/, \A\z/, /^/m, /$/m 在 Perl 中匹配空字符串。这是一个平台问题吗?所有文档都说一样的事情。很奇怪! - user557597
@stema 你是怎么知道的?我的意思是,这个行为看起来像是在引用是否可以匹配 ^。 - Ingo

2
让我们更仔细地看一下你的第二个例子:
Pattern p2 = Pattern.compile("^$", Pattern.MULTILINE);
Matcher m2 = p2.matcher("");
System.out.println(m2.matches()); /* false */

所以你有一行在m2中,它是空的或只包含换行符号,没有其他字符。因此,为了与给定的行相对应,你的模式应该只是 "$",即:

// Your example
Pattern p2 = Pattern.compile("^$", Pattern.MULTILINE);
Matcher m2 = p2.matcher("");
System.out.println(m2.matches()); /* false */

// Let's check if it is start of the line
p2 = Pattern.compile("^", Pattern.MULTILINE);
m2 = p2.matcher("");
System.out.println(m2.matches()); /* false */

// Let's check if it is end of the line
p2 = Pattern.compile("$", Pattern.MULTILINE);
m2 = p2.matcher("");
System.out.println(m2.matches()); /* true */

它没有回答提问者的问题,即"^$"在没有启用MULTILINE模式时匹配成功,但是在启用MULTILINE模式后却失败了。 - anubhava
@anubhava 这是因为我们没有序列的开头,只有结尾。根据JDK API: 在多行模式下,表达式^和$分别匹配行终止符或输入序列的末尾之后或之前。默认情况下,这些表达式仅匹配整个输入序列的开头和结尾。(http://docs.oracle.com/javase/1.4.2/docs/api/java/util/regex/Pattern.html)在单行模式下,我们得到一个空字符串,在多行模式下得到一个空序列。 - wanderlust
在Perl中,多行模式下的“^$”匹配空字符串,那么这是Perl中的一个错误吗? - user557597

1

听起来像是一个bug。在多行模式下,“^”和“$”最多可以被解释为匹配内部行边界。Java可能没有像Perl一样的扩展变量状态结构。我不知道这是否是原因。

事实上,/^test$/m匹配只是证明了^ $在多行模式下工作,除非字符串为空(在Java中),但显然多行模式测试空字符串是荒谬的,因为/^$/适用于此。

在Perl中进行测试,一切都按预期工作:

if ( "" =~ /^$/m   ) { print "/^\$/m    matches\n"; }
if ( "" =~ /^$/    ) { print "/^\$/     matches\n"; }
if ( "" =~ /\A\Z/m ) { print "/\\A\\Z/m  matches\n"; }
if ( "" =~ /\A\Z/  ) { print "/\\A\\Z/   matches\n"; }
if ( "" =~ /\A\z/  ) { print "/\\A\\z/   matches\n"; }
if ( "" =~ /^/m    ) { print "/^/m     matches\n"; }
if ( "" =~ /$/m    ) { print "/\$/m     matches\n"; }


__END__


/^$/m    matches
/^$/     matches
/\A\Z/m  matches
/\A\Z/   matches
/\A\z/   matches
/^/m     matches
/$/m     matches

同意,我测试了在.NET中使用/^$/m/^$/,它们都按预期工作。 - stema

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接