同一段正则,在JAVA和JavaScript里结果相反?

494 阅读2分钟

今天踩了一个小坑,公司业务要求前端表单字段的验证由后端返回,于是自然而然的将返回的正则直接放进构造函数,这时问题就来了。

校验的规则是输入以6或者8开头的8位数字,后端返回的正则字符串是这样的,并且信誓旦旦的说验证过没问题。

^6\\d{7}|8\\d{7}$

然后当我放到RegExp里却发现,验证是有问题的。只要以6或者8开头,後面跟著至少7個數字(大于或者等于7),也可以通过。

const reg = new RegExp('^6\\d{7}|8\\d{7}$') 
reg.test('66666666666666')
// true 

这次我才仔细看这段看起来平常的正则表达式,才发现了问题。 在js的正则规则中,這裡的管道符號|表示“或”,並且它沒有被括號包圍,所以它實際上是將正則表達式分成了兩部分:^6\d{7}8\d{7}$。這意味著它會匹配数字6加上7个任意数字作为开头 6*******,或者以数字8加上任意7个数字8*******作为结尾的字符串。这是有问题的,因为没有限制到位数。

我理所应当的认为,正则表达式的规则与语言是无关的,后端一定是写错了。当我将这个问题反馈之后,后端却告诉我,他验证过并且确实输出的false

String number = "66666666666666";
String regex = "^6\\d{7}|8\\d{7}$";
System.out.println(number.matches(regex));
// false

这就非常奇怪了,但是随着后端换了一种写法这个问题也就不了了之。后来闲暇之余查了一些资料, 原来JavaScript和Java中的正则表达式规则是有差异的。 例如上述提到的这个问题,之所以出现相反的结果,就是因为 管道符 | 的优先级不一致。

在JavaScript中,这个正则表达式会匹配包含以6开头的8位数字或者包含以8+7个数字作为结尾的8位数字。因此,'66666666666666’会被匹配成功,因为它包含了一个以6开头的8位数字。

然而,在Java中,由于没有明确指定分组,所以"^“和”"被应用到了整个表达式,而不仅仅是它们各自的一部分。这意味着整个输入字符串必须完全匹配"6\d{7}"或者"8\d{7}",也就是说,整个输入字符串必须是以6开头的8位数字或者以8开头的8位数字。因此,'66666666666666’在Java中无法被匹配成功。

其实对于上面这种情况解决方法也很简单,在表达式中加上括号创建明确的分组即可。

/(^6\d{7}$)|(^8\d{7}$)/

这里只是为了对比举例,实际上如果想要验证6或者8开头的8位数字,有更清晰、简短的正则表达式。

例如/^[6,8]\d{7}$/

不算什么很大的知识点,但是如果遇到了还是会浪费一些时间,所以作此分享。