Java replaceAll 替换值含美元符号时报 Illegal group reference
Java replaceAll 的替换字符串有捕获组引用和反斜杠转义规则。字面替换值使用 Matcher.quoteReplacement,区分模式引用与替换引用,避免破坏原本需要的捕获组模板。
正则已经能匹配要替换的内容,替换值一出现 $,String.replaceAll() 却抛出异常:
String text = " $$";
String result = text.replaceAll("\\s+\\$\\$", "$$");
// java.lang.IllegalArgumentException: Illegal group reference
问题在第二个参数。replaceAll(regex, replacement) 的匹配模式和替换字符串使用不同的语法:替换字符串里的 $ 可以引入捕获组引用,反斜杠也有转义作用。第一参数已经正确匹配字面美元符号,不会让第二参数自动变成普通文本。
下面使用标准 Java 字符串与正则 API,Matcher.quoteReplacement() 从 Java 5 起提供。具体异常文本可能随 JDK 版本变化,排查时应定位到替换调用及其实际替换值。
把需要原样插入的值交给 quoteReplacement
如果替换值应按普通文本使用,在传入替换接口前处理该值:
import java.util.regex.Matcher;
public class LiteralReplacement {
public static void main(String[] args) {
String text = " $$";
String replacement = "$$";
String result = text.replaceAll(
"\\s+\\$\\$",
Matcher.quoteReplacement(replacement)
);
System.out.println(result);
// 预期 $$
}
}
quoteReplacement() 会让这个替换值中的美元符号和反斜杠按字面内容进入结果。它没有改变第一参数的匹配范围:这里仍然匹配空白后面的两个美元符号,不会删除文本中的所有空白。
动态替换值可能同时包含反斜杠和美元符号,只手工替换某一种字符容易漏掉边界。也不要对已经处理过的值反复调用 quoteReplacement();应保存原始业务值,在进入需要替换语法的接口时处理一次。
Java 源码字符串还有自己的转义规则。例如源码中的 "C:\\temp\\$1" 表示运行时字符串 C:\temp\$1。从文件或接口读入的文本已经是运行时内容,不需要再人为增加一层“Java 源码转义”。
Pattern.quote 不能代替替换值处理
Pattern.quote() 处理的是匹配模式中的字面文本,Matcher.quoteReplacement() 处理的是替换位置中的字面文本。把第二个参数包在 Pattern.quote() 中,并不能按这条接口契约关闭替换组引用。
如果只有一部分匹配模式来自字面配置,可以只处理这一部分,保留其余正则功能:
String marker = "$$";
String replacement = "$5";
String result = " $$".replaceAll(
"\\s+" + java.util.regex.Pattern.quote(marker),
java.util.regex.Matcher.quoteReplacement(replacement)
);
// 预期 $5
不要把整个 "\\s+" 模式一起当普通文本引用,否则原本用于匹配空白的规则也会变成字面字符。检查时分别记录匹配模式和原始替换值,敏感业务文本不要直接输出到公共日志。
完全不需要正则时,用字面替换接口
如果目标和替换内容都只是普通字符串,String.replace(CharSequence, CharSequence) 可以直接按字面替换所有匹配片段:
String result = "cost=TOKEN".replace("TOKEN", "$5");
// 预期 cost=$5
这个重载从 Java 5 起提供。它同样会替换所有出现的目标串,名称没有 All 不表示只替换第一次。
但它不能直接代替开头的正则示例:字面目标 "\\s+\\$\\$" 不会匹配“空白加两个美元符号”。只有业务确实不需要正则匹配时才切换接口。
本来要使用捕获组时,保留对应语法
有些替换值中的 $1 正是业务需要的引用,不能统一转成字面量:
String result = "name=alice".replaceAll(
"name=(\\w+)",
"$1"
);
// 预期 alice
如果对这里的 "$1" 使用 quoteReplacement(),结果会变成字面文本 $1。应先明确该字段是普通替换内容还是允许组引用的模板;两类输入可以走不同入口,不能靠字符串里有没有美元符号猜意图。
修复后覆盖普通文本、美元符号、反斜杠、形似组引用的 $1、没有任何匹配的输入,以及原本需要捕获组的模板。既检查不再抛异常,也检查结果是否保留了预期字符。保留原始输入和旧替换规则,必要时回退局部调用;已经写出的错误替换结果不会随代码回退而恢复,需要从原始数据重新生成。


