Java String 的 replaceFirst 和 replaceAll 詳解


首先我們來看 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”會被看作一般字符串而不會有特殊的處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM