1. 概述

字符串操作是 Java 开发中的核心技能之一,而 String.replaceAll() 方法则是处理文本替换的利器。本文将深入解析这个方法的工作原理,并通过实际案例展示其强大功能。

2. String.replaceAll() 方法解析

replaceAll() 允许我们通过正则表达式匹配字符串中的模式,并用指定内容替换匹配项。它本质上是正则表达式驱动的替换工具,而非简单的文本替换。

方法签名如下:

public String replaceAll(String regex, String replacement)

参数说明:

  • regex:用于匹配的正则表达式
  • replacement:替换匹配项的字符串

基础示例:

String input = "Hello w o r l d";
String result = input.replaceAll("\\s", "_");
assertEquals("Hello_w_o_r_l_d", result);

这里用 \\s 匹配所有空白字符,替换为下划线。

再看一个复杂示例:

result = input.replaceAll("e.*o", "X");
assertEquals("HX r l d", result);

正则 e.*o 会匹配从第一个 'e' 开始到最后一个 'o' 结束的所有字符(贪婪匹配)。若需非贪婪匹配(匹配到第一个 'o' 即停止):

result = input.replaceAll("e.*?o", "X");  // 非贪婪匹配
assertEquals("HX w o r l d", result);

result = input.replaceAll("e[^o]*o", "X");  // 等效写法
assertEquals("HX w o r_l_d", result);

⚠️ 注意:.*?[^o]* 都能实现非贪婪匹配,但后者明确排除了 'o' 字符。

3. replace() vs replaceAll() 对比

虽然 replace()replaceAll() 有时结果相同,但核心差异在于是否支持正则表达式

String input = "hello.java.hello.world";

// 字面量替换(结果相同)
String replaceResult = input.replace("hello", "hi");
assertEquals("hi.java.hi.world", replaceResult);

String replaceAllResult = input.replaceAll("hello", "hi");
assertEquals("hi.java.hi.world", replaceAllResult);

但当处理特殊字符时差异明显:

// 替换点号时结果不同
replaceResult = input.replace(".", ":");  // 字面量替换
assertEquals("hello:java:hello:world", replaceResult);

replaceAllResult = input.replaceAll(".", ":");  // 正则替换(.匹配任意字符)
assertEquals("::::::::::::::::::::::", replaceAllResult);

❌ 踩坑提醒:replaceAll() 中的 . 是正则元字符,需转义处理!

4. 正则特殊字符处理

正则中的特殊字符(如 ., [], (), | 等)需要特殊处理才能作为字面量匹配。两种解决方案

方案1:反斜杠转义

String input = "hi.java.hi.world";
String result = input.replaceAll("\\.", ":");  // 转义点号
assertEquals("hi:java:hi:world", result);

方案2:字符组包裹

result = input.replaceAll("[.]", ":");  // 用字符组包裹
assertEquals("hi:java:hi:world", result);

处理括号示例:

input = "(debug) hello.world";

// ❌ 错误:括号被当作分组符
result = input.replaceAll("(debug)", "[info]");
assertEquals("([info]) hello.world", result);

// ✅ 正确:转义或字符组处理
result = input.replaceAll("\\(debug\\)", "[info]");
assertEquals("[info] hello.world", result);

result = input.replaceAll("[(]debug[)]", "[info]");
assertEquals("[info] hello.world", result);

5. 无效正则的处理

当传入无效正则表达式时,replaceAll() 会抛出 PatternSyntaxException

String input = "Hello world";
assertThrows(PatternSyntaxException.class, () -> input.replaceAll("e**", "X"));

⚠️ 原因:e** 中第二个 * 缺少前置元素,属于非法正则语法。

6. 超越简单替换的高级用法

6.1 捕获组引用

通过 $n 引用捕获组实现复杂重组:

String input = "123456789";
String expected = "789-456-123";

// 数字分组引用
String result = input.replaceAll("(\\d{3})(\\d{3})(\\d{3})", "$3-$2-$1");
assertEquals(expected, result);

// 命名捕获组引用
result = input.replaceAll(
    "(?<first>\\d{3})(?<second>\\d{3})(?<third>\\d{3})", 
    "${third}-${second}-${first}"
);
assertEquals(expected, result);

✅ 命名捕获组优势:提升复杂正则的可读性和可维护性。

6.2 零宽断言插入分隔符

使用零宽断言在字符间插入分隔符(不消耗字符):

String input = "abcdefg";
String expected = "a-b-c-d-e-f-g";

String result = input.replaceAll("(?<=.)(?=.)", "-");
assertEquals(expected, result);

正则解析:

  • (?<=.):正向后行断言(当前位置前有字符)
  • (?=.):正向先行断言(当前位置后有字符)
  • 匹配位置:所有相邻字符之间的"间隙"

7. 总结

String.replaceAll() 是 Java 中强大的文本处理工具,其核心价值在于正则表达式的深度集成。通过本文我们掌握了:

  • 基础替换与正则模式匹配
  • replace() 的本质区别
  • 特殊字符的转义处理
  • 捕获组引用和零宽断言等高级技巧

完整示例代码见 GitHub 仓库