2. 问题场景介绍
在Java开发中,我们经常需要提取字符串中括号内的文本。当输入仅包含一对括号时,可以通过两次replaceAll()
调用简单实现:
String myString = "a b c (d e f) x y z";
String result = myString.replaceAll(".*[(]", "")
.replaceAll("[)].*", "");
assertEquals("d e f", result);
✅ 原理:第一次调用删除(
之前的所有字符,第二次调用删除)
之后的所有字符。
但当输入包含多对括号时,这种方法就失效了。例如:
static final String INPUT = "a (b c) d (e f) x (y z)";
我们期望提取出以下结果:
static final List<String> EXPECTED = List.of("b c", "e f", "y z");
接下来我们探讨几种高效解决方案。
3. 贪婪 vs 非贪婪正则表达式
正则表达式是处理此类问题的利器。初学者可能会尝试这种模式:
String myRegex = "[(](.*)[)]";
Matcher myMatcher = Pattern.compile(myRegex)
.matcher(INPUT);
List<String> myResult = new ArrayList<>();
while (myMatcher.find()) {
myResult.add(myMatcher.group(1));
}
assertEquals(List.of("b c) d (e f) x (y z"), myResult);
❌ 踩坑警告:结果只匹配到"b c) d (e f) x (y z"
,因为.*
是贪婪匹配,会从第一个(
匹配到最后一个)
。
解决方案:使用非贪婪匹配(在*
后加?
):
String regex = "[(](.*?)[)]";
List<String> result = new ArrayList<>();
Matcher matcher = Pattern.compile(regex)
.matcher(INPUT);
while (matcher.find()) {
result.add(matcher.group(1));
}
assertEquals(EXPECTED, result);
✅ 关键点:.*?
确保每次匹配到最近的)
就停止。
4. 使用否定字符类
除了非贪婪量词,还可以用否定字符类[^)]
:
String regex = "[(]([^)]*)";
List<String> result = new ArrayList<>();
Matcher matcher = Pattern.compile(regex)
.matcher(INPUT);
while (matcher.find()) {
result.add(matcher.group(1));
}
assertEquals(EXPECTED, result);
模式解析:
[(]
:匹配左括号([^)]*)
:捕获组,匹配任意非)
字符- ⚠️ 注意:
[^)]
会持续匹配直到遇到)
,完美避开贪婪问题
进阶技巧:使用正向回顾断言(lookbehind)简化模式:
String regex = "(?<=[(])[^)]*";
List<String> result = new ArrayList<>();
Matcher matcher = Pattern.compile(regex)
.matcher(INPUT);
while (matcher.find()) {
result.add(matcher.group());
}
assertEquals(EXPECTED, result);
✅ 优势:(?<=\[(\])
是零宽断言,不消耗字符,无需额外捕获组。
5. 使用Apache Commons Lang3的StringUtils
如果项目已集成Apache Commons Lang3,可直接使用StringUtils
:
// 单对括号场景
String myString = "a b c (d e f) x y z";
String result = StringUtils.substringBetween(myString, "(", ")");
assertEquals("d e f", result);
// 多对括号场景
String[] results = StringUtils.substringsBetween(INPUT, "(", ")");
assertArrayEquals(EXPECTED.toArray(), results);
✅ 推荐场景:当项目已依赖该库时,这是最简洁的方案。
6. 总结
本文探讨了Java中提取括号文本的四种方案:
- 基础正则:适合单括号场景
- 非贪婪匹配:解决多括号贪婪问题
- 否定字符类:高效避免回溯
- StringUtils工具类:依赖库时的最佳实践
完整示例代码见GitHub仓库