【Java学習|初心者向け】Java正規表現の落とし穴!「最短一致(非貪欲)」で思い通りの検索を実現しよう

1. 導入:なぜ「最短一致」を知る必要があるのか?

Javaで文字列の抽出を行う際、正規表現は非常に強力なツールです。しかし、正規表現を使い始めたばかりのエンジニアが最初につまずくのが「意図した部分だけが取れない」という問題です。デフォルトの挙動(貪欲な量指定子)は、マッチする最大の範囲を探しに行ってしまいます。この課題を解決し、必要な箇所だけをピンポイントで切り出すために必須となるのが「消極的な量指定子(最短一致)」です。

2. 基礎知識:貪欲と消極の違い

正規表現の量指定子( や + など)は、標準では「貪欲(Greedy)」に動作します。これは「できるだけ長い文字列にマッチさせようとする」性質です。
一方、「消極的(Reluctant)」な量指定子は、後ろに「?」を付けることで「条件を満たす最小限の文字列で止める」ように指示します。

代表的な変換表は以下の通りです。
・(0回以上) → ?(0回以上、最短で)
・+(1回以上) → +?(1回以上、最短で)
・?(0か1回) → ??(0か1回、最短で)
・{n,}(n回以上) → {n,}?(n回以上、最短で)

3. 実装:HTMLタグの抽出で見る違い

例えば「<b>Hello</b>」という文字列から「<b>」と「</b>」の間の文字を取り出したいとします。
貪欲な指定子「.」を使うと、「<b>」から最後の「</b>」まで全てを飲み込んでしまいます。ここで「.?」を使うことで、「最初の</b>が出てきたところで止める」という動作が可能になります。

4. サンプルプログラム

以下のコードをコピーして、Javaの環境で動作を確認してみてください。

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

public class RegexExample {
public static void main(String[] args) {
String input = “タイトル 内容“;

// 貪欲な指定子(.)の場合:全体がマッチしてしまう
Pattern greedyPattern = Pattern.compile(“(.)“);
Matcher greedyMatcher = greedyPattern.matcher(input);

// 消極的な指定子(.?)の場合:個別にマッチする
Pattern reluctantPattern = Pattern.compile(“(.?)“);
Matcher reluctantMatcher = reluctantPattern.matcher(input);

System.out.println(“— 貪欲な検索結果 —“);
if (greedyMatcher.find()) {
// “タイトル 内容” が出力されてしまう(期待と違う)
System.out.println(“マッチ内容: ” + greedyMatcher.group(1));
}

System.out.println(“\n— 消極的な検索結果 —“);
while (reluctantMatcher.find()) {
// “タイトル” と “内容” が別々に抽出される(期待通り)
System.out.println(“マッチ内容: ” + reluctantMatcher.group(1));
}
}
}

5. 応用・注意点:現場での活用と落とし穴

Named Groups(名前付きグループ)との併用
Java 7以降では、(?…) を使うことでマッチした箇所に名前を付けられます。最短一致と組み合わせることで、「抽出したいデータ」がどこにあるのかをコード上で明確にできます。
例: Pattern.compile(“(?.?)“);

注意点:パフォーマンスへの影響
最短一致(.?)は、マッチする箇所を探すたびに「次に進むべきか、ここで止めるべきか」を細かく判定するため、非常に長い文字列に対して使うと、貪欲な検索よりもパフォーマンスが低下する場合があります。データ量が膨大な場合は、正規表現ではなく、StringクラスのsubstringやindexOfなどを組み合わせた処理の方が高速なケースもあることを覚えておきましょう。

まずは「思い通りの範囲が取れないな」と思ったら、迷わず「?」を付けてみる。これが最短一致を使いこなす第一歩です!

コメント

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