Java replaceAll 方法的「天坑」

好吧,我承认我可能标题党了,其实大部分所谓的「坑」都是由于自己的无知所造成的。首先来说说我遇到的问题吧,我的一个项目里需要在 Java 环境下从服务器获取一段 JSON,然后拼接成一个 JavaScript 函数调用语句,传递给 WebView 中的页面去执行,由于是拼接的语句,所以 JSON 中的引号我们还需要进行一次转义,于是我理所当然地写下了这行代码:

String escaped = json.replaceAll("\"", "\\\"");

乍一看,貌似真没什么问题,但当我执行的时候,我发现它根本就 NOT WORKING!!于是我向 GoogleStackOverflow 求救,他们告诉我需要这样写:

String escaped = json.replaceAll("\"", "\\\\\"");

WTF??为什么会有五个反斜杠?但是时间紧迫我也没有深入研究这个问题,只是在知乎留下了一个问题:「Java 引号为什么要这样转义?」。

后来我看到了知友的回答,说实话,看到的一瞬间我就恍然大悟了,他的回答也没再看完。

所以是什么问题呢?咱们看看 replaceAll 这个方法的文档:

Replaces each substring of this string that matches the given regular expression with the given replacement.

An invocation of this method of the form
str.replaceAll(regex, repl)
yields exactly the same result as the expression
...
Note that backslashes () and dollar signs ($) 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);


这里有个小 tip
如果你使用 IntelliJ IDEA 的话,把正则表达式在记事本中写好再复制回 IDE,它会帮你自动转义,还是很方便的。

这段程序执行的结果就是:

中英文 mix 在一起

Process finished with exit code 0

符合我们的预期。到这里我们应该能凭直觉得出,如果要将文本替换成 $ 的话,我们就还需要转义,也就是写成 \$,同理,如果我们要使用 \ 的话,也需要转义,也就是写成 \\,那么文章一开头的那个例子中,把 " 替换成 \" 的话,第二个参数我们就需要写成 \\",我们在 IDE 外部复制它,再粘贴到代码中,IDE 帮我们再作一次转义就得到 \\\\\" 了。

理顺一下:

  • 我们需要一个 \ 来给替换函数转义 \
  • 我们还需要在每个 \ 前再加一个 \ 来给 Java 编译器转义
  • 我们再还需要一个 \ 来给 Java 编译器转义 "

最后就是五个 \ 了。就是这么简单,希望你还没晕 ;-)

你可能感兴趣的:(Java replaceAll 方法的「天坑」)