【Java学習|実務向け】Java正規表現を極める:Matcherクラスによる柔軟な文字列置換とストリーム処理

導入

Javaでの文字列操作において、単純なString.replace()では対応できない複雑なパターンマッチングや置換が必要になるケースは多々あります。特に、動的な値を埋め込んだり、マッチした内容に基づいて置換ロジックを変えたりする場合、java.util.regex.Matcherクラスのメソッド群を使いこなすことが不可欠です。本稿では、実務で頻出するMatcherの置換系メソッドと、Java 9から導入された便利なストリーム処理を解説します。

基礎知識

正規表現を扱うには、まずパターンを定義する「Pattern」クラスと、対象文字列に対してマッチングを行う「Matcher」クラスを利用します。
Named groups(名前付きキャプチャグループ)は、正規表現内で(?<名前>…)のように記述することで、インデックス番号ではなく名前でマッチした部分文字列を取得できる機能です。これにより、コードの可読性と保守性が飛躍的に向上します。

実装/解決策

単純な一括置換には「replaceAll()」や「replaceFirst()」が便利ですが、マッチした箇所ごとにロジックを挟みたい場合は「appendReplacement()」と「appendTail()」をループ内で組み合わせるのが定石です。また、結果をStreamとして扱いたい場合は「results()」メソッドを用いることで、関数型プログラミングスタイルで処理を記述できます。

サンプルプログラム

以下のコードは、名前付きグループを使用して文字列を置換し、さらにStream APIを使ってマッチ結果を収集する例です。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class RegexExample {
    public static void main(String[] args) {
        String input = "注文ID: 101, 商品: Apple; 注文ID: 102, 商品: Banana";
        // 名前付きグループを使用してIDと商品を抽出するパターン
        Pattern pattern = Pattern.compile("ID: (?\\d+), 商品: (?\\w+)");

        // 1. appendReplacementを利用した動的な置換処理
        Matcher matcher = pattern.matcher(input);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            // マッチしたグループ名を指定して置換文字列を作成
            String replacement = String.format("[ID:%s/商品:%s]", 
                                 matcher.group("id"), matcher.group("item").toUpperCase());
            // マッチした部分を置換し、StringBufferに格納
            matcher.appendReplacement(sb, replacement);
        }
        // 残りの文字列を末尾に追加
        matcher.appendTail(sb);
        System.out.println("置換結果: " + sb.toString());

        // 2. results()を利用したStream処理(Java 9以降)
        System.out.println("抽出結果: " + pattern.matcher(input).results()
                .map(mr -> mr.group("item"))
                .collect(Collectors.toList()));
    }
}

応用・注意点

1. StringBufferの使い道:
appendReplacement()メソッドは、可変文字列としてStringBuilderではなくStringBufferを要求します。これはJavaの歴史的な経緯によるものですが、マルチスレッド環境でMatcherを使い回すことは推奨されないため、基本的にはローカル変数として利用してください。

2. 名前付きグループの利便性:
正規表現が複雑になると、グループのインデックス($1, $2など)を管理するのは非常に困難です。可能な限り名前付きグループを活用し、コードの意図を明確にしましょう。

3. パフォーマンスへの配慮:
ループ内でPattern.compile()を呼び出すと、都度コンパイルが発生しパフォーマンスが低下します。Patternオブジェクトはstatic final定数として定義し、再利用することを強くお勧めします。また、非常に長い文字列に対して正規表現を適用する場合、バックトラックによるスタックオーバーフローやCPU負荷に注意してください。

コメント

タイトルとURLをコピーしました