Matcher 物件


在建立 Pattern 實例之後,可以使用 matcher 方法指定要比對的字串,這會傳回 java.util.regex.Matcher 實例,表示對指定字串的比對器,可以使用 find 方法來測試是否有下個符合字串,或是使用 lookingAt 看看字串開頭是否符合規則表示式,使用 group 方法則可以傳回符合的字串。例如:

package cc.openhome;

import static java.lang.System.out;
import java.util.regex.*;

public class MatcherDemo {
    public static void main(String[] args) {
        var regexs = {".*foo", ".*?foo", ".*+foo"};
        for(String regex : regexs) {
            var pattern = Pattern.compile(regex);
            var matcher = pattern.matcher("xfooxxxxxxfoo");
            out.printf("%s find ", pattern.pattern());
            while(matcher.find()) {
                out.printf(" \"%s\"", matcher.group());
            }
            out.println(" in \"xfooxxxxxxfoo\".");
        }
    }
}

這個範例示範了貪婪、逐步與獨吐量詞的比對結果,執行結果如下:

.*foo find  "xfooxxxxxxfoo" in "xfooxxxxxxfoo".
.*?foo find  "xfoo" "xxxxxxfoo" in "xfooxxxxxxfoo".
.*+foo find  in "xfooxxxxxxfoo".

如果規則表示式中有分組,group 可以接受 int 整數指定分數計數,舉例來說,規則表示式如果是 ((A)(B(C))),若指定文字為 ABC,matcher.find 後指定 group(1) 就是 "ABC"group(2) 就是 "A"group(3) 就是 "BC"group(4) 就是 "C",由於分組計數會從 1 開始,因此 group(0) 就相當於直接呼叫沒有參數的 group()

如果設定了命名分組,group 方法可以指定名稱來取得分組:

jshell> var regex = Pattern.compile("(?<user>^[a-zA-Z]+\\d*)@(?<preCom>[a-z]+?.)com");
regex ==> (?<user>^[a-zA-Z]+\d*)@(?<preCom>[a-z]+?.)com

jshell> var matcher = regex.matcher("caterpillar@openhome.com");
matcher ==> java.util.regex.Matcher[pattern=(?<user>^[a-zA-Z] ... om region=0,24 lastmatch=]

jshell> matcher.find();
$3 ==> true

jshell> matcher.group("user");
$4 ==> "caterpillar"

jshell> matcher.group("preCom");
$5 ==> "openhome." 

Matcher 還有 replaceAll 方法,可以將符合規則表示式的部份以指定的字串取代,效果等同於 StringreplaceAll 方法,replaceFirstreplaceEnd 可分別取代首個、最後符合規則表示式的部份;start 方法可以取得符合字串的起始索引,end 方法可取得符合字串最後一個字元後的索引。

如果規則表示中有分組設定,在使用 replaceAll 時,可以使用 $n 來捕捉被分組匹配的文字(使用於 StringreplaceAllreplaceFirst 也可以)。

例如,以下的片段可以將使用者郵件位址從 .com 取代為 .cc:

var pattern = Pattern.compile("(^[a-zA-Z]+\\d*)@([a-z]+?.)com");
var matcher = pattern.matcher("caterpillar@openhome.com");
out.println(matcher.replaceAll("$1@$2cc")); // caterpillar@openhome.cc

如果是命名分組,使用的是 ${name} 形式:

jshell> var regex = Pattern.compile("(?<user>^[a-zA-Z]+\\d*)@(?<preCom>[a-z]+?.)com");
regex ==> (?<user>^[a-zA-Z]+\d*)@(?<preCom>[a-z]+?.)com

jshell> var matcher = regex.matcher("caterpillar@openhome.com");
matcher ==> java.util.regex.Matcher[pattern=(?<user>^[a-zA-Z] ... om region=0,24 lastmatch=]

jshell> matcher.replaceAll("${user}@${preCom}cc");
$8 ==> "caterpillar@openhome.cc"

Matcher 的狀態顯然是可變的,如果目前狀態取得的比對結果,不想被後續比對影響,可以使用 toMatcherResult 方法取得 MatcherResult 實作物件,傳回的物件是不可變、只包含該次比對狀態;實際上,Matcher 也實作了 MatcherResult 介面。

Java 9 對 Matcher 作了些增強,首先,appendReplacementappendTail 現在有了接受 StringBuilder 的版本:

var regex = Pattern.compile("cat(?<plural>z?s?)");
var matcher = regex.matcher("one catz two cats in the yard");
var sb = new StringBuilder();
while (matcher.find()) {
    matcher.appendReplacement(sb, "dog${plural}");
}

matcher.appendTail(sb);
out.println(sb.toString()); // "one dogz two dogs in the yard"

replaceAllreplaceFirst 也多了個接受 Function<MatchResult,String> 的版本,因此可以自訂取代函式:

jshell> var regex = Pattern.compile("(^[a-zA-Z]+\\d*)@([a-z]+?.)com");
regex ==> (^[a-zA-Z]+\d*)@([a-z]+?.)com

jshell> var matcher = regex.matcher("caterpillar@openhome.com");
matcher ==> java.util.regex.Matcher[pattern=(^[a-zA-Z]+\d*)@( ... om region=0,24 lastmatch=]

jshell> matcher.replaceAll(result -> String.format("%s@%scc", result.group(1), result.group(2)));
$11 ==> "caterpillar@openhome.cc"

另外還有個 results 方法,可傳回 Stream<MatchResult> 實例,便於透過 Stream API 操作:

jshell> var matcher = regex.matcher("Justin+Monica+Irene");
matcher ==> java.util.regex.Matcher[pattern=\pL+ region=0,19 lastmatch=]

jshell> matcher.results().map(result -> result.group().toUpperCase()).forEach(out::println);
JUSTIN
MONICA
IRENE