導入
Javaで正規表現を扱う際、PatternとMatcherは非常に強力なツールですが、実務で意外と見落とされがちなのが「Matcherの状態」です。特に、一つのMatcherオブジェクトをループ内で再利用しようとした際、「期待した結果が得られない」「マッチングがスキップされる」といったバグに遭遇したことはありませんか?その解決の鍵となるのが、Matcher.reset()メソッドです。本記事では、このメソッドの重要性と正しい使い方を解説します。
基礎知識
Javaの正規表現エンジンは、java.util.regex.Patternとjava.util.regex.Matcherの組み合わせで動作します。
Patternは正規表現のコンパイル済みの表現であり、Matcherは、ある文字列に対してPatternを照合するエンジンです。
ここで重要なのは、Matcherが「ステートフル(状態を持つ)」なオブジェクトであるという点です。find()メソッドなどで検索を行うと、Matcher内部には「次にどこから検索を開始すべきか」というインデックス情報が保持されます。一度マッチングが終了すると、内部ポインタは文字列の末尾に到達しており、そのままでは再検索ができません。
実装/解決策
Matcherの状態を初期化するには、reset()メソッドを使用します。このメソッドを呼び出すことで、内部の検索位置が0にリセットされ、最初から検索をやり直すことができます。また、特定の文字列に対して再利用したい場合は、reset(CharSequence input)に新しい文字列を渡すことで、Matcherインスタンスを生成し直すコストを抑えつつ、別の文字列を検索対象に切り替えることも可能です。
サンプルプログラム
以下のコードは、一つのMatcherインスタンスを使い回して、異なる文字列に対して繰り返しマッチングを行う実用的な例です。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
public static void main(String[] args) {
// 名前付きグループを使用して「日付」を抽出するパターン
Pattern pattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher matcher = pattern.matcher("");
String[] dates = {"2023-10-01", "2024-05-20"};
for (String date : dates) {
// reset(input)で新しい文字列をセットしつつ、状態を初期化する
matcher.reset(date);
if (matcher.find()) {
// 名前付きグループで値を取得
String y = matcher.group("year");
String m = matcher.group("month");
String d = matcher.group("day");
System.out.println("日付: " + date + " -> 年:" + y + " 月:" + m + " 日:" + d);
}
}
}
}
応用・注意点
現場での開発において注意すべき点は、以下の2点です。
1. インスタンスの使い回しとスレッド安全性: Matcherはスレッドセーフではありません。マルチスレッド環境で一つのMatcherを共有すると深刻なバグになります。ループ内での使い回しは、あくまでシングルスレッドの処理範囲に留めてください。
2. パフォーマンスの最適化: ループのたびにPattern.compile()を行うのはコストが高いです。Patternはstatic finalな定数として定義し、Matcherは必要に応じてreset()で使い回すのが、パフォーマンスとメモリ効率の観点からベストプラクティスです。
Matcherは「一度使い終わったらそのまま」ではなく、「再利用のためにresetする」という意識を持つだけで、正規表現周りのトラブルを大幅に減らすことができます。ぜひ実務のコードで活用してください。

コメント