首先我們來看 API 文檔。
-
replaceFirst
public String replaceFirst(String regex, String replacement)
用 給定的 replacement 字符串參數 來替換 被給定的正則表達式(regex 字符串參數)匹配的此字符串的 第一個子字符串。str.replaceFirst(regex, repl)
的結果與以下表達式 的結果完全相同Pattern.compile(regex).matcher(str).replaceFirst(repl)
請注意,替換字符串 replacement 中的反斜杠(
\
)和美元符號($
)可能會導致結果與被視為一般替換字符串時的結果不同; 見Matcher.replaceFirst(java.lang.String)
。 如果需要,使用Matcher.quoteReplacement(java.lang.String)
來抑制這些字符的特殊含義。- 參數
-
regex
- 要匹配此字符串的正則表達式 -
replacement
- 要替換第一個匹配的字符串 - 返回
-
被替換后的新
String
- 拋出
-
PatternSyntaxException
- 如果正則表達式的語法無效 - 從以下版本開始:
- 1.4
-
replaceAll
public String replaceAll(String regex, String replacement)
用 給定的 replacement 字符串參數 來替換 被給定的正則表達式(regex 字符串參數)匹配的此字符串的 每個子字符串。str.replaceAll(regex, repl)
的結果與以下表達式 的結果完全相同Pattern.compile(regex).matcher(str).replaceAll(repl)
請注意,替換字符串 replacement 中的反斜杠(
\
)和美元符號($
)可能會導致結果與被視為一般替換字符串時的結果不同; 見Matcher.replaceAll
。 如果需要,使用Matcher.quoteReplacement(java.lang.String)
來抑制這些字符的特殊含義。- 參數
-
regex
- 要匹配此字符串的正則表達式 -
replacement
- 要替換每個匹配的字符串 - 返回
-
被替換后的新
String
- 拋出
-
PatternSyntaxException
- 如果正則表達式的語法無效 - 從以下版本開始:
- 1.4
由以上 Java API 文檔我們可以看到,String.replaceFirst 和 String.replaceAll 都把第一個參數(regex 字符串參數)視為正則表達式。replaceFirst 只替換字符串中第一個被匹配到的子串,而 replaceAll 會替換字符串中所有被匹配到的子串。
下面我們來看一個例子(在 System.out.println 之后同一行的注釋為其輸出,在 System.out.println 一行下面的注釋是對輸出的說明)。我用這個例子是因為這里面包含了我踩過的一個坑:"\
" 在 Java 源碼字符串中(編譯期)和在正則表達式中(運行期)都被用來標識轉義字符。
final String origin = "a\\b\\c/d/ef\\g";
System.out.println(origin); // a\b\c/d/ef\g
// \\ 中第一個 \ 是轉義字符的標識
System.out.println(origin.replaceFirst("\\\\", ".")); // a.b\c/d/ef\g
// 由 Java 字符串的語法,第一個參數實際上是兩個 \ ,又因為 replaceFirst 把
// 第一個參數看成是正則表達式,而 \ 在正則表達式里也是轉義字符的標識,所以
// "\\\\" 實際上是只匹配一個 \ 字符的正則表達式。
// 這個結果是把 origin 字符串的第一個“\”替換成了“.”。
System.out.println(origin.replaceAll("\\\\", ".")); // a.b.c/d/ef.g
// 這個結果是把 origin 字符串的所有“\”替換成了“.”。
如果 replaceFirst 或者 replaceAll 的第一個參數正好是"\\
"(一個"\
"),那么在運行時編譯正則表達式的時候就會拋異常:
try {
System.out.println("a\\b".replaceFirst("\\", "."));
} catch (java.util.regex.PatternSyntaxException ex) {
System.err.println(ex.getMessage());
// Unexpected internal error near index 1
// \
// ^
}
try {
System.out.println("a\\b".replaceAll("\\", "."));
} catch (java.util.regex.PatternSyntaxException ex) {
System.err.println(ex.getMessage());
// Unexpected internal error near index 1
// \
// ^
}
下面我們來解釋 API 文檔里的這句話“請注意,替換字符串 replacement 中的反斜杠(\
)和美元符號($
)可能會導致結果與被視為一般替換字符串時的結果不同”是什么意思。
replacement 參數中的美元符號的作用
在這兩個方法里,replacement 參數中的美元符號($
)加一個序號代表正則表達式匹配結果中的第幾個組(表達式在匹配時,表達式引擎會將小括號 "()
" 包含的表達式所匹配到的字符串記錄下來。在獲取匹配結果的時候,小括號包含的表達式所匹配到的字符串可以單獨獲取。)我們用一些例子來說明:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("([a-z])/([a-z])", "x$1y$2")); // a\bxcyd/e/f\g
// 這個正則表達式匹配被“/”分隔的兩個英文字母,
// origin 字符串中被匹配到的第一個子串是“c/d”,
// 其中第 1 組是第一個英文字母“c”,第 2 組是第二個英文字母“d”
// 代入第二個參數“x$1y$2”之后,就是要把“c/d”替換為“xcyd”
System.out.println(origin.replaceAll("([a-z])/([a-z])", "x$1y$2")); // a\bxcyd/xeyf\g
// origin 字符串中還可以被匹配到第二個子串“e/f”,
// 其中第 1 組是第一個英文字母“e”,第 2 組是第二個英文字母“f”
// 代入第二個參數“x$1y$2”之后,就是要把“e/f”替換為“xeyf”
為了方便大家理解,我把以上代碼改一下顏色,用相同的顏色來標識(this 字符串內)被匹配到的子串、(regex 參數)正則表達式里的分組和替換后的字符串(返回值)里對應正則表達式分組的內容:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("([a-z])/([a-z])", "x$1y$2")); // a\bxcyd/e/f\g
System.out.println(origin.replaceAll ("([a-z])/([a-z])", "x$1y$2")); // a\bxcyd/xeyf\g
在正則表達式里,第 0 組代表被匹配到的整個子串。我們可以用如下例子來看看 "$0
" 在 replaceFirst 或 replaceAll 中的效果:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("([a-z])/", "x$1y$0")); // a\b\xcyc/d/e/f\g
// 這個正則表達式匹配一個小寫英文字母及緊隨其后的一個“/”。
// 第一個被匹配到的子串是“c/”,它要被替換成“xcyc/”。
System.out.println(origin.replaceAll("([a-z])/", "x$1y$0")); // a\b\xcyc/xdyd/xeye/f\g
// 還可以匹配到第二個子串“d/”,它要被替換成“xdyd/”。
// 還可以匹配到第三個子串“e/”,它要被替換成“xeye/”。
為了方便大家理解,我把以上代碼改一下顏色:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("([a-z])/", "x$1y$2")); // a\bxcyc/d/e/f\g
System.out.println(origin.replaceAll ("([a-z])/", "x$1y$2")); // a\bxcyc/xdyd/xeye/f\g
如果 replacement 參數中的美元符號($
)后面沒有序號或者不是數字,那么就會拋 IllegalArgumentException,請看如下代碼示例:
final String origin = "a\\b\\c/d/e/f\\g";
try {
System.out.println(origin.replaceFirst("([a-z])/", "x$1y$"));
// 最后的“$”后沒有序號
} catch(IllegalArgumentException ex) {
System.err.println(ex.getMessage());
// Illegal group reference: group index is missing
}
try {
System.out.println(origin.replaceAll("([a-z])/", "x\\$y$0"));
// 第一個“$”后面是“y”
} catch(IllegalArgumentException ex) {
System.err.println(ex.getMessage());
// Illegal group reference
}
replacement 參數中的反斜杠的作用
那么如果我們就是需要把子串替換成含有 "$
" 的字符串怎么辦呢?這時候就需要 API 文檔里提到的另一個特殊字符(轉義字符)"\
"。請看如下代碼示例:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("([a-z])/", "x\\$1y\\$0")); // a\b\x$1y$0d/e/f\g
// 要記得代碼里的字符串里的“\\”對應運行時的字符串里的一個“\”
// 由於“$”之前有轉義字符標識“\”,所以它會被看作是一個普通的字符而不是代表正則表達式中的組
// 第一個被匹配到的子串是“c/”,它要被替換成“x$1y$0”。
System.out.println(origin.replaceAll("([a-z])/", "x\\$1y\\$0")); // a\b\x$1y$0x$1y$0x$1y$0f\g
// 還可以匹配到第二個子串“d/”,它要被替換成“x$1y$0”。
// 還可以匹配到第三個子串“e/”,它要被替換成“x$1y$0”。
由於 replacement 參數中的反斜杠(\
)被看作是轉義字符的標識,如果我們就是需要把子串替換成含有 "\
" 的字符串,那么我們就要用兩個反斜杠 "\\
":
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceAll("/", "\\\\")); // a\b\c\d\e\f\g
// 要記得代碼里的字符串里的“\\”對應運行時的字符串里的一個“\”
如果 replacement 參數中的反斜杠(\
)后面沒有要被轉義的字符,那么就會拋 IllegalArgumentException,請看如下代碼示例:
final String origin = "a\\b\\c/d/e/f\\g";
try {
System.out.println(origin.replaceFirst("/", "\\"));
// 最后的“\”后沒有字符
} catch(IllegalArgumentException ex) {
System.err.println(ex.getMessage());
// character to be escaped is missing
}
與 String.replace 的比較
對 Java 的 String API 不熟悉的人可能會將這兩個方法與 String.replace 方法混淆,因為這 3 個方法都可以接受兩個字符串參數。而從以下 Java API 文檔中我們知道,String.replace 方法是將第一個參數看成一般文本而不是正則表達式。
-
replace
public String replace(CharSequence target, CharSequence replacement)
將與字面目標序列匹配的字符串的 每個子字符串替換為指定的字面替換序列。 替換從字符串開始到結束,例如,在字符串“aaa”中用“b”替換“aa”將導致“ba”而不是“ab”。- 參數
-
target
- 要替換的char值序列 -
replacement
- char值的替換順序 -
返回
-
被替換后的新
String
- 從以下版本開始:
- 1.5
其次,String.replace 方法和 String.replaceAll 方法一樣,會替換所有被匹配到的子串。我們再用一個代碼的實例來說明:
final String origin = "a\\b\\c/d/e/f\\g"; // a\b\c/d/e/f\g
System.out.println(origin.replaceFirst("\\\\", "x$0y")); // ax\yb\c/d/e/f\g
System.out.println(origin.replaceAll("\\\\", "x$0y")); // ax\ybx\ycd/e/fx\yg
System.out.println(origin.replace("\\\\", "x$0y")); // a\b\c/d/e/f\g
// origin 字符串中沒有連續的兩個“\”
System.out.println(origin.replace("\\", "x$0y")); // ax$0ybx$0yc/d/e/fx$0yg
// origin 字符串中的所有“/”都會被替換而不只是第一個
// 第二個參數里的“$0”會被看作一般字符串而不會有特殊的處理