Javaプログラミングにおいて、文字列の加工は避けて通れない非常に重要な処理の一つです。

ユーザーからの入力値を整形したり、ログから特定のパターンを抽出して書き換えたり、あるいは機密情報をマスクしたりと、その用途は多岐にわたります。

その中でも Stringクラスの replaceAll メソッド は、正規表現(Regular Expression)を活用することで、複雑な条件に合致する文字列を一括で置換できる強力なツールです。

しかし、Javaには似た名称の replace メソッドも存在しており、初心者から中級者にかけて「どちらを使うべきか」「正規表現の記述でエラーが出る」といった悩みに直面することも少なくありません。

本記事では、replaceAll メソッドの基本的な使い方から、正規表現を用いた応用テクニック、replace メソッドとの決定的な違いについて、具体的なコード例を交えながら詳しく解説していきます。

JavaのreplaceAllメソッドとは

Javaの java.lang.String クラスに用意されている replaceAll メソッドは、指定した「正規表現」に一致する部分を、すべて別の文字列に置き換えるためのメソッドです。

Javaにおける文字列(String型)は不変(Immutable)であるため、元の文字列を直接書き換えるのではなく、置換処理を施した新しい文字列を戻り値として返却するという特徴があります。

基本的な構文

replaceAll メソッドのシグネチャは以下の通りです。

Java
public String replaceAll(String regex, String replacement)
  • regex: 置換対象を特定するための正規表現パターンを指定します。
  • replacement: 置き換え後の文字列を指定します。

このメソッドを呼び出すと、第一引数の正規表現にマッチする箇所がすべて第二引数の文字列に置換されます。

もし一致する箇所が一つも見つからなかった場合は、元の文字列と同じ内容の新しいインスタンスが返されます。

replaceAllの基本的な使用例

まずは、最もシンプルな例として、特定の文字列パターンを別の文字に置き換えるコードを見てみましょう。

Java
public class ReplaceAllBasic {
    public static void main(String[] args) {
        String original = "Apple, Banana, Apple, Orange";
        
        // "Apple" という文字列をすべて "Grape" に置換
        String result = original.replaceAll("Apple", "Grape");
        
        System.out.println("元の文字列: " + original);
        System.out.println("置換後: " + result);
    }
}
実行結果
元の文字列: Apple, Banana, Apple, Orange
置換後: Grape, Banana, Grape, Orange

この例では、正規表現としての特殊記号を使用していないため、単純な文字列の置換として機能しています。

しかし、replaceAll の真価は 正規表現によるパターンマッチング にあります。

replaceAll と replace の違い

Javaには、replaceAll のほかに replace メソッドも存在します。

名前が似ているため混同されやすいのですが、これらには明確な役割の違いがあります。

引数の扱いの違い

最大の違いは、第一引数を「正規表現」として扱うか「リテラル(純粋な文字)」として扱うかという点です。

メソッド名第一引数の型意味
replaceCharSequence / char指定した文字・文字列をそのまま(リテラルとして)置換する。
replaceAllString指定した文字列を「正規表現」として解釈して置換する。

動作の比較コード

以下の例で、動作の違いを確認してみましょう。

Java
public class ReplaceVsReplaceAll {
    public static void main(String[] args) {
        String text = "price is 100. tax is 10.";
        
        // replaceメソッド: "." をリテラルとして扱う
        String replacedByReplace = text.replace(".", "!");
        
        // replaceAllメソッド: "." は正規表現で「任意の1文字」を意味する
        String replacedByReplaceAll = text.replaceAll(".", "!");
        
        System.out.println("replaceの結果:    " + replacedByReplace);
        System.out.println("replaceAllの結果: " + replacedByReplaceAll);
    }
}
実行結果
replaceの結果:    price is 100! tax is 10!
replaceAllの結果: !!!!!!!!!!!!!!!!!!!!!!!!

replace メソッドは、文字列内のドット . をそのまま文字として探し、置換しています。

一方で replaceAll メソッドの場合、正規表現においてドットは「任意の1文字」という意味を持つため、すべての文字が感嘆符に置き換わってしまいます

どちらを使うべきか?

基本的には以下の基準で使い分けるのがベストプラクティスです。

replace

置換したい文字が固定されており、正規表現を使う必要がない場合。

動作が直感的で、正規表現の解析コストがかからないためパフォーマンス的にも有利です。

replaceAll

「数字だけを消したい」「複数のスペースを一つにまとめたい」など、複雑なパターンを指定したい場合。

正規表現を活用した高度な置換

replaceAll の強力な点は、正規表現のメタ文字を駆使できることです。

よく使われるテクニックをいくつか紹介します。

数字の置換・除去

文字列の中から数字(0-9)だけを探して置換する場合は、正規表現の \d または [0-9] を使用します。

Java
public class RegexDigitExample {
    public static void main(String[] args) {
        String input = "User ID: 12345, Order: 67890";
        
        // すべての数字を '*' にマスクする
        String masked = input.replaceAll("\\d", "*");
        
        // すべての数字を削除する
        String removed = input.replaceAll("[0-9]", "");
        
        System.out.println("マスク後: " + masked);
        System.out.println("削除後:   " + removed);
    }
}
実行結果
マスク後: User ID: *****, Order: *****
削除後:   User ID: , Order:

空白文字の整理

文章中の半角スペース、全角スペース、タブなどが混在している場合に、それらをまとめて置換したい場合は \s を使用します。

Java
public class WhitespaceExample {
    public static void main(String[] args) {
        String text = "Java   is \t fun\n";
        
        // 1つ以上の連続する空白文字を1つのスペースに集約
        String cleanText = text.replaceAll("\\s+", " ");
        
        System.out.println("整形前: [" + text + "]");
        System.out.println("整形後: [" + cleanText.trim() + "]");
    }
}
実行結果
整形前: [Java   is 	 fun
]
整形後: [Java is fun]

\s++ は「1回以上の繰り返し」を意味します。

これにより、複数のスペースが連続していても、1つのスペースに置換されます。

後方参照(Back Reference)の利用

replaceAll の非常に便利な機能として、「検索したパターンの一部を置換後の文字列で再利用する」というものがあります。

これを「後方参照」と呼びます。

正規表現内で丸括弧 () を使ってグループ化すると、置換後の文字列内で $1, $2 といった記法でその内容を参照できます。

日付フォーマットの変換例

例えば、「2024/05/20」という形式の文字列を「2024年05月20日」に変換したい場合を考えます。

Java
public class BackReferenceExample {
    public static void main(String[] args) {
        String date = "2024/05/20";
        
        // (数字4桁) / (数字2桁) / (数字2桁) をグループ化
        String regex = "(\\d{4})/(\\d{2})/(\\d{2})";
        
        // $1, $2, $3 で各グループの内容を参照
        String formatted = date.replaceAll(regex, "$1年$2月$3日");
        
        System.out.println("変換前: " + date);
        System.out.println("変換後: " + formatted);
    }
}
実行結果
変換前: 2024/05/20
変換後: 2024年05月20日

このように、パターンにマッチした動的な値を保持したまま、周囲の文字だけを変更するといった高度な操作が可能です。

注意点とハマりやすいポイント

replaceAll は強力ですが、いくつか注意すべき仕様があります。

1. バックスラッシュのエスケープ

Javaの文字列リテラルにおいて、バックスラッシュ \ はエスケープシーケンス(\n など)として扱われます。

そのため、正規表現の \d を記述したい場合は、Javaコード上では \d と二重に書く必要があります。

さらに、正規表現におけるメタ文字(. * + ? ^ $ ( ) [ { | \)そのものを文字として置換したい場合は、\ を付けてエスケープしなければなりません。

例えば、文字列内の「$」を置換したい場合は以下のようになります。

Java
String price = "100$";
// "$" は行末を意味するメタ文字なので "\\$" と書く必要がある
String result = price.replaceAll("\\$", "円");

2. NullPointerExceptionの発生

replaceAll を呼び出す対象の文字列が null である場合、当然ながら NullPointerException が発生します。

また、引数(regex や replacement)に null を渡した場合も例外が発生するため、事前に null チェックを行うか、Optional 等で保護することが推奨されます。

3. パフォーマンスへの影響

replaceAll メソッドは、内部で毎回正規表現のコンパイル(Patternオブジェクトの生成)を行っています。

そのため、ループの中で大量に replaceAll を呼び出す処理を書くと、パフォーマンスが著しく低下する恐れがあります。

大量の処理を行う場合は、以下のように Pattern クラスをあらかじめコンパイルしておく方法を検討してください。

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

public class PerformanceOptimization {
    // 事前にコンパイルしておく(再利用可能)
    private static final Pattern PATTERN = Pattern.compile("\\d+");

    public static String fastReplace(String input, String replacement) {
        Matcher matcher = PATTERN.matcher(input);
        return matcher.replaceAll(replacement);
    }
}

実践的な活用シーン

ここでは、実際の開発現場でよく遭遇する replaceAll の活用例を紹介します。

HTMLタグの除去

Webスクレイピングなどで取得したテキストから、HTMLタグ(<div> など)をすべて取り除いて中身のテキストだけを抽出したい場合です。

Java
public class StripHtmlExample {
    public static void main(String[] args) {
        String html = "<div class='content'>Hello <b>World</b>!</div>";
        
        // <と>で囲まれた任意の部分を置換
        String text = html.replaceAll("<[^>]*>", "");
        
        System.out.println("結果: " + text);
    }
}
実行結果
結果: Hello World!

※注:複雑なHTML構造を正規表現だけで完全に解析するのは限界がありますが、単純なタグ除去には非常に有効です。

電話番号のハイフン統一

ユーザーが入力した電話番号の形式がバラバラ(ハイフンあり、なし、スペース区切り)な場合に、特定の形式に統一します。

Java
public class PhoneFormatExample {
    public static void main(String[] args) {
        String phone1 = "090-1234-5678";
        String phone2 = "080 9876 5432";
        String phone3 = "07011112222";
        
        String regex = "[^0-9]"; // 数字以外すべて
        
        System.out.println(phone1.replaceAll(regex, ""));
        System.out.println(phone2.replaceAll(regex, ""));
        System.out.println(phone3.replaceAll(regex, ""));
    }
}
実行結果
09012345678
08098765432
07011112222

このように、不要な記号を一括で排除して、クリーンな数値データのみを得ることができます。

replaceFirstとの使い分け

replaceAll と似たメソッドに replaceFirst があります。

  • replaceAll: 合致するすべての箇所を置換する。
  • replaceFirst: 最初に見つかった1箇所目だけを置換する。

例えば、ログファイルの最初のタイムスタンプだけを書き換えたい場合などは replaceFirst を使用します。

Java
String log = "2024-01-01 Error: System failure. 2024-01-01 Info: Logging stopped.";
String result = log.replaceFirst("\\d{4}-\\d{2}-\\d{2}", "DATE");
// 結果: "DATE Error: System failure. 2024-01-01 Info: Logging stopped."

すべてを対象にする必要がない場合は、意図しない箇所を置換してしまわないよう、より限定的な replaceFirst を選択するのも一つの手です。

まとめ

Javaの replaceAll メソッドは、正規表現を自在に操ることで、単なる文字列置換の枠を超えた複雑なテキスト処理を可能にします。

今回のポイントを整理すると以下のようになります。

  • replace と replaceAll の違い:前者はリテラル、後者は正規表現として引数を扱う。
  • 正規表現の威力\d(数字)や \s(空白)などを使ってパターンマッチングが可能。
  • 後方参照($1, $2):マッチした内容の一部を置換後にも利用できる。
  • 注意点:エスケープ文字(\)の扱いや、パフォーマンス面での考慮が必要。

文字列操作は、システムの品質やデータ整合性に直結する部分です。

まずは replace で済むシンプルな置換か、それとも replaceAll が必要な複雑なパターンかを正しく判断できるようになりましょう。

正規表現のパターン学習を並行して進めることで、Javaでの開発効率は劇的に向上するはずです。

適切なメソッドを選択し、バグの少ないクリーンなコードを目指してください。