1. 概述
正则表达式(Regular Expressions)在 Java 中广泛应用于文本处理,比如输入校验、日志解析、关键词提取等场景。其中,一个常见需求是:统计某段文本中正则表达式匹配的次数。
本文将详细介绍如何使用 Java 实现这一功能,并对比 Java 8 与 Java 9+ 的不同实现方式,帮你避免踩坑,写出更简洁高效的代码。
2. 使用场景
我们以一个实际场景为例:统计一段文本中合法邮箱地址出现的次数。
为了识别邮箱,我们使用一个简化的正则表达式(仅用于演示):
([a-z0-9_.-]+)@([a-z0-9_.-]+[a-z])
⚠️ 注意:真实的邮箱校验正则非常复杂(RFC 5322),这里只是为了演示目的做了极大简化,实际项目中建议使用 Apache Commons Validator 或更严谨的模式。
我们将该正则封装为一个 Pattern
常量:
Pattern EMAIL_ADDRESS_PATTERN =
Pattern.compile("([a-z0-9_.-]+)@([a-z0-9_.-]+[a-z])");
测试文本包含三个邮箱:
String TEXT_CONTAINING_EMAIL_ADDRESSES =
"You can contact me through user@example.com, admin@site.org, and test@domain.net";
目标是正确识别出 3 个匹配项。
3. Java 8 及更早版本的实现方式
在 Java 8 及之前,最直接的方式是使用 Matcher.find()
方法配合循环遍历。
✅ 核心思路:find()
方法会从上一次匹配结束位置继续查找下一个匹配项,直到文本末尾。
示例代码如下:
Matcher countEmailMatcher = EMAIL_ADDRESS_PATTERN.matcher(TEXT_CONTAINING_EMAIL_ADDRESSES);
int count = 0;
while (countEmailMatcher.find()) {
count++;
}
assertEquals(3, count);
⚠️ 注意事项
find()
不会重置匹配器状态,而是从上次匹配的结尾继续搜索。- 因此,它无法匹配重叠(overlapping)的子串。
举个例子:
String OVERLAPPING_EMAIL_ADDRESSES = "Try to contact us at user@@baeldung.com, admin@site.com.";
在这个字符串中,user@@baeldung.com
实际上不是一个合法邮箱,但我们的正则会匹配到:
user@
(前半部分) ❌@baeldung.com
(跳过第一个 @ 后的合法部分)❌admin@site.com
✅
但由于 find()
是非重叠查找,它只会找到两个有效匹配:
- 第一次匹配
user@
(假设前面有合法用户名) - 第二次匹配
admin@site.com
而中间的 @baeldung.com
因为紧接在第一个匹配之后,不会被重新检查。
最终结果:
Matcher countOverlappingEmailsMatcher = EMAIL_ADDRESS_PATTERN.matcher(OVERLAPPING_EMAIL_ADDRESSES);
int count = 0;
while (countOverlappingEmailsMatcher.find()) {
count++;
}
assertEquals(2, count); // 实际只找到两个
如图所示,匹配过程是线性推进的,跳过了潜在的重叠区域。
4. Java 9 及更高版本的实现方式
从 Java 9 开始,Matcher
类新增了一个非常实用的方法:results()
。
✅ results()
返回一个 Stream<MatchResult>
,表示所有匹配结果的流,我们可以直接调用 count()
统计数量。
代码变得极其简洁:
long count = EMAIL_ADDRESS_PATTERN
.matcher(TEXT_CONTAINING_EMAIL_ADDRESSES)
.results()
.count();
assertEquals(3, count);
✅ 优势
- 无需手动写循环
- 函数式风格,代码更清晰
- 可与其他 Stream 操作组合(如过滤、去重等)
⚠️ 注意点
- 和
find()
一样,results()
也是非重叠匹配 - 匹配位置依然从上一次结束处开始,不支持重叠子串检测
所以对于上面的重叠邮箱例子,结果仍然是 2:
long count = EMAIL_ADDRESS_PATTERN
.matcher(OVERLAPPING_EMAIL_ADDRESSES)
.results()
.count();
assertEquals(2, count);
5. 总结
方式 | Java 版本 | 是否推荐 | 说明 |
---|---|---|---|
find() + while 循环 |
Java 8 及以下 | ✅ | 兼容性好,逻辑清晰 |
results().count() |
Java 9+ | ✅✅✅ | 更现代、简洁,推荐新项目使用 |
📌 关键要点:
- 正则匹配默认是非重叠的,不要指望靠
find()
或results()
检测重叠模式 - 如果真有重叠匹配需求(极少见),需要手动控制索引或使用其他工具库
- 邮箱正则不要自己造轮子,生产环境建议使用成熟方案(如
javax.mail.internet.InternetAddress
校验)
所有示例代码已上传至 GitHub:
👉 https://github.com/baeldung/java-tutorials/tree/master/core-java-regex