Java Tips | 文字列処理:正規表現置換

Java Java
スポンサーリンク

正規表現置換は「パターンに合うところだけ賢く書き換える」技

正規表現マッチが「パターンに合う部分を見つける」技だとしたら、
正規表現置換は「パターンに合う部分だけを、狙った形に書き換える」技です。

「電話番号のハイフンを揃えたい」「ログから個人情報をマスクしたい」「余計な空白やタグを削りたい」――
こういう“ルールに沿った一括変換”を、1行で表現できるのが正規表現置換の強みです。


Java の基本:String.replaceAll と Matcher.replaceAll

まずは「パターンに合うところを全部置き換える」

一番よく使うのは String#replaceAll です。

String text = "電話: 03-1234-5678 / FAX: 03-9876-5432";
String masked = text.replaceAll("\\d", "X");
System.out.println(masked);
// 電話: XX-XXXX-XXXX / FAX: XX-XXXX-XXXX
Java

ここで押さえたいのは二つです。

一つ目は、「第一引数は“正規表現”として解釈される」ということです。
\d は「数字1文字」を意味する正規表現で、Java の文字列リテラルでは \\d と書きます。

二つ目は、「replaceAll は“マッチしたすべての部分”を置き換える」ということです。
一箇所だけでよければ replaceFirst、全部なら replaceAll、と覚えておくとよいです。


業務で使いやすくするためのユーティリティ化

「パターンを事前コンパイルして再利用する」型

replaceAll は手軽ですが、同じ正規表現を何度も使うなら Pattern を事前コンパイルしておく方が効率的です。

import java.util.regex.Pattern;

public final class RegexReplacer {

    private RegexReplacer() {}

    public static String replaceAll(String text, String regex, String replacement) {
        if (text == null) {
            return null;
        }
        Pattern p = Pattern.compile(regex);
        return p.matcher(text).replaceAll(replacement);
    }
}
Java

使い方はこうです。

String text = "ID: ABC-123-XYZ";
String result = RegexReplacer.replaceAll(text, "\\d", "X");
System.out.println(result); // ID: ABC-XXX-XYZ
Java

ここでの重要ポイントは、「Pattern.matcher(...).replaceAll(...) という形を覚えておく」ことです。
この形にしておくと、後で「同じパターンを何度も使う」「グループを使った高度な置換をする」ときに、そのまま拡張できます。


例題1:ログの個人情報をマスクする

電話番号やメールアドレスを「見せてもいい形」に変える

ログにそのまま個人情報を出したくない、というのは業務で非常によくある要件です。
例えば、「電話番号の下4桁だけマスクする」ユーティリティを作ってみます。

import java.util.regex.Pattern;

public final class Masking {

    // 例: 03-1234-5678 → 03-1234-****
    private static final Pattern PHONE_PATTERN =
            Pattern.compile("(\\d{2,4}-\\d{2,4}-)\\d{4}");

    private Masking() {}

    public static String maskPhone(String text) {
        if (text == null) {
            return null;
        }
        return PHONE_PATTERN.matcher(text).replaceAll("$1****");
    }
}
Java

使い方はこうです。

String log = "電話: 03-1234-5678 / 携帯: 090-1111-2222";
System.out.println(Masking.maskPhone(log));
// 電話: 03-1234-**** / 携帯: 090-1111-****
Java

ここで深掘りしたい重要ポイントは、「キャプチャグループと $1 などの“後方参照”を組み合わせている」ことです。

(\\d{2,4}-\\d{2,4}-) で「先頭〜2つ目のハイフンまで」をグループ1としてキャプチャし、
\\d{4} で「末尾4桁」をマッチさせています。

置換側の $1**** は、「グループ1で取った部分をそのまま残し、末尾4桁だけ **** にする」という意味になります。
この「一部を残しつつ、一部だけ書き換える」のが、正規表現置換の一番おいしいところです。


例題2:余計な空白やタブを一括で整形する

「連続する空白を1つにまとめる」「行頭・行末の空白を削る」

ログや入力値には、「スペースやタブがぐちゃっと入っている」ことがよくあります。
これを一括で整えるユーティリティを作ってみます。

import java.util.regex.Pattern;

public final class Whitespaces {

    private static final Pattern MULTI_SPACE = Pattern.compile("\\s+");

    private Whitespaces() {}

    public static String normalizeSpaces(String text) {
        if (text == null) {
            return null;
        }
        // 連続する空白を1つのスペースに
        String once = MULTI_SPACE.matcher(text).replaceAll(" ");
        // 行頭・行末の空白を削る
        return once.trim();
    }
}
Java

使い方はこうです。

String raw = "  山田\t\t太郎   さん  ";
String normalized = Whitespaces.normalizeSpaces(raw);
System.out.println("\"" + normalized + "\"");
// "山田 太郎 さん"
Java

ここでのポイントは、「正規表現置換と通常のメソッド(trim)を組み合わせて、“一連の整形ルール”をユーティリティに閉じ込めている」ことです。
呼び出し側は normalizeSpaces だけ呼べば、「空白をいい感じに整える」という意図を一行で表現できます。


例題3:タグや特定パターンを丸ごと削除する

「HTMLタグをざっくり落とす」「制御コードを消す」

ログや外部入力に HTML っぽいものが混ざることがあります。
完璧な HTML パーサではなく、「タグっぽいものをざっくり落としたい」というケースなら、正規表現置換で十分なことも多いです。

import java.util.regex.Pattern;

public final class Sanitizer {

    private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]+>");

    private Sanitizer() {}

    public static String removeTags(String text) {
        if (text == null) {
            return null;
        }
        return TAG_PATTERN.matcher(text).replaceAll("");
    }
}
Java

使い方はこうです。

String htmlish = "<b>重要</b>なお知らせです。<br>確認してください。";
System.out.println(Sanitizer.removeTags(htmlish));
// 重要なお知らせです。確認してください。
Java

ここでの重要ポイントは、「“完璧な HTML 処理”ではなく、“ログやメッセージ用にざっくり落とす”という用途を明確にしている」ことです。
正規表現で HTML を完全に扱うのは無理がありますが、「タグっぽいものを消す」程度なら十分実用的です。


置換でつまずきやすいポイント

置換文字列の中の $ と \

replaceAll の第二引数(置換文字列)も、少しだけ特別なルールがあります。

$1 のような形は「キャプチャグループの後方参照」として解釈されます。
もし文字としての $ を出したい場合は、\\$ とエスケープする必要があります。

同様に、\ も特別扱いされるので、文字としてのバックスラッシュを出したいときは、
Java 文字列リテラルと合わせて "\\\\" のように二重にエスケープが必要になります。

「正規表現の世界」と「Java 文字列リテラルの世界」が二重に効いてくるので、
ややこしく感じたら、紙に「正規表現としてどう書きたいか」「Java ではどう書くか」を書き出して整理すると楽になります。

replace と replaceAll の違いを混同しない

String には replacereplaceAll の両方がありますが、役割が違います。

replace は「文字列そのものの置換」で、正規表現は使いません。
replaceAll は「第一引数を正規表現として解釈して置換」します。

例えば、. をそのまま置き換えたいなら replace(".", ",") でよく、
「数字だけを全部 X にしたい」なら replaceAll("\\d", "X") を使う、というように使い分けます。


まとめ:正規表現置換ユーティリティで身につけたい感覚

正規表現置換は、「パターンに合う部分だけを賢く書き換える」ための、とても強力な道具です。

押さえておきたい感覚は、まず「replaceAll は“正規表現ベースの一括変換”であり、matches と同じくパターンの世界にいる」ということ。
次に、「よく使う置換(マスク、空白整形、タグ削除など)は Pattern を事前コンパイルしたユーティリティとして固定し、プロジェクト全体で同じルールを共有する」こと。
そして、「キャプチャグループ+後方参照($1 など)を使うことで、“一部を残しつつ一部だけ変える”という高度な置換を、短く安全に書ける」ことです。

もしあなたのコードのどこかに、text.replaceAll("...", "...") が生で散らばっているなら、
その一つを題材にして、ここで作った MaskingWhitespacesSanitizer のようなユーティリティクラスに移してみてください。
それだけで、「読めて、再利用できて、仕様変更にも強い正規表現置換」に、一段レベルアップできます。

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