Java replaceAll 方法的「天坑」
好吧,我承认我可能标题党了,其实大部分所谓的「坑」都是由于自己的无知所造成的。首先来说说我遇到的问题吧,我的一个项目里需要在 Java 环境下从服务器获取一段 JSON,然后拼接成一个 JavaScript 函数调用语句,传递给 WebView 中的页面去执行,由于是拼接的语句,所以 JSON 中的引号我们还需要进行一次转义,于是我理所当然地写下了这行代码:
String escaped = json.replaceAll("\"", "\\\"");
乍一看,貌似真没什么问题,但当我执行的时候,我发现它根本就 NOT WORKING!!于是我向 Google 和 StackOverflow 求救,他们告诉我需要这样写:
String escaped = json.replaceAll("\"", "\\\\\"");
WTF??为什么会有五个反斜杠?但是时间紧迫我也没有深入研究这个问题,只是在知乎留下了一个问题:「Java 引号为什么要这样转义?」。
后来我看到了知友的回答,说实话,看到的一瞬间我就恍然大悟了,他的回答也没再看完。
所以是什么问题呢?咱们看看 replaceAll
这个方法的文档:
Replaces each substring of this string that matches the given <a href="#">regular expression</a> with the given replacement.
<p> An invocation of this method of the form
<i>str</i><tt>.replaceAll(</tt><i>regex</i><tt>,</tt> <i>repl</i><tt>)</tt>
yields exactly the same result as the expression
...
Note that backslashes (<tt></tt>) and dollar signs (<tt>$</tt>) in the
replacement string may cause the results to be different than if it were
being treated as a literal replacement string; see
{@link java.util.regex.Matcher#replaceAll Matcher.replaceAll}.
Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special
meaning of these characters, if desired.
明白了吗,replaceAll
的第一个参数接受一个正则表达式,这个我们应该都能理解,但有的时候我们像在被替换的内容中引用这个正则所捕获到的内容。试举一例,假设有一个字符串 "中英文mix在一起",我们想要将 “mix” 这个单词和中文文字之间用空格空开(我们都知道这是最规范的写法),那么在替换时我们肯定还需要引用到被找到的不规范字符串子串,那么替换内容就是:[空格] + mix + [空格]
。
那么在 Java 中如何引用被捕获的子串呢?那就是用 $
修饰符。我们可以尝试一下:
String s = "中英文mix在一起";
s = s.replaceAll("((?<=[^\\x00-\\xff])[a-zA-Z]+)|([a-zA-Z]+(?=[^\\x00-\\xff]))", " $0 ");
System.out.println(s);
<strong>这里有个小 tip</strong>
如果你使用 IntelliJ IDEA 的话,把正则表达式在记事本中写好再复制回 IDE,它会帮你自动转义,还是很方便的。
这段程序执行的结果就是:
中英文 mix 在一起
Process finished with exit code 0
符合我们的预期。到这里我们应该能凭直觉得出,如果要将文本替换成 $
的话,我们就还需要转义,也就是写成 \$
,同理,如果我们要使用 \
的话,也需要转义,也就是写成 \\
,那么文章一开头的那个例子中,把 "
替换成 \"
的话,第二个参数我们就需要写成 \\"
,我们在 IDE 外部复制它,再粘贴到代码中,IDE 帮我们再作一次转义就得到 \\\\\"
了。
理顺一下:
- 我们需要一个
\
来给替换函数转义\
- 我们还需要在每个
\
前再加一个\
来给 Java 编译器转义 - 我们再还需要一个
\
来给 Java 编译器转义"
最后就是五个 \
了。就是这么简单,希望你还没晕 ;-)