本ページはプロモーションが含まれています

Java入門

Java正規表現入門!基本と実践例でマスター

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

「Javaの正規表現って、なんだか難しそう…」

「特殊な記号が多くて、どこから手をつければいいかわからない」

プログラミング学習を進める中で、このように感じた経験はありませんか。私自身、Javaを学び始めたころは正規表現の独特なルールにかなり戸惑いました。特に、バックスラッシュを2つ重ねる「二重エスケープ」の壁にぶつかり、何度もエラーを出したものです。

しかし、実務でWebアプリケーションの開発に携わる中で、入力値のチェックやログの解析など、正規表現が必須となる場面に数多く直面しました。何百ものパターンを書き、試行錯誤を重ねるうちに、だんだんとその便利さと強力さを実感できるようになったのです。

この記事では、過去の私と同じようにJavaの正規表現でつまずいている方に向けて、その基本から実践的な使い方までを網羅的に解説します。

この記事を読み終えるころには、あなたもJavaの正規表現を使いこなし、文字列操作の効率を格段にアップさせることができるでしょう。

正規表現とは?Javaで使う意味を理解しよう

最初に、Javaで正規表現を扱ううえでの基本的な考え方や、その役割について見ていきましょう。

正規表現の基本的な考え方

正規表現とは、文字列のパターンを表現するための特殊な文字列です。ひと言でいうと「文字列のルールブック」のようなもの。

例えば、「3桁の数字-4桁の数字」という形式の郵便番号を探したい場合、「数字が3つ、次にハイフン、最後に数字が4つ」というパターンを正規表現で表現します。このパターンを使えば、たくさんの文章の中から郵便番号だけを正確に見つけ出せるのです。

このように、特定のルールに合った文字列を扱うための強力なツールが正規表現です。

Javaで正規表現を使う主な場面(文字検索・置換・バリデーション)

Javaプログラミングにおいて、正規表現は主に次の3つの場面で活躍します。

  1. 文字列の検索・抽出大量のテキストデータから、特定のパターンに一致する部分だけを探し出す場面です。例えば、WebページのHTMLソースからURLだけをすべて抜き出したり、ログファイルからエラーメッセージだけを抽出したりする際に使われます。
  2. 文字列の置換パターンに一致した部分を、別の文字列に置き換える処理です。個人情報保護のために、文章中の電話番号をすべて「***」のような伏字に置き換える、といった用途で活用できます。
  3. バリデーション(妥当性検証)ユーザーが入力したデータが、決められた形式に沿っているかチェックする場面で非常に役立ちます。メールアドレスや電話番号、パスワードの強度など、特定のフォーマットを守らせたい場合に必須の技術です。

PatternとMatcherクラスの関係

Javaで正規表現を扱う際には、主にjava.util.regexパッケージに含まれるPatternクラスMatcherクラスを利用します。

  • Patternクラス: 作成した正規表現のパターンを、コンピュータが理解できる形式にコンパイル(変換・準備)するためのクラスです。料理でいう「レシピ」に相当します。
  • Matcherクラス: コンパイルされたPatternを使い、実際に特定の文字列に対してマッチング処理(照合)を行うためのクラスです。「レシピ」をもとに調理を行う「シェフ」と考えると分かりやすいでしょう。

まずPatternクラスで文字列のルールを定義し、次にMatcherクラスでそのルールを対象の文字列に適用する、という流れが基本です。

Javaの正規表現の基本構文をマスターしよう

正規表現を使いこなすには、その根幹となる「メタ文字」と基本構文の理解が欠かせません。

メタ文字(.・*・+など)の意味

正規表現では、特別な意味を持つ文字を「メタ文字」と呼びます。これらを組み合わせることで、複雑な文字列のパターンを表現します。代表的なメタ文字をいくつか紹介します。

メタ文字意味
.任意の一文字(改行文字を除く)
*直前の文字の0回以上の繰り返し
+直前の文字の1回以上の繰り返し
?直前の文字が0回または1回出現する
^行の先頭
$行の末尾
[abc]aまたはbまたはcのいずれか一文字
[^abc]abc以外のいずれか一文字
[a-z]aからzまでのいずれか一文字
( )グループ化
\d半角数字[0-9]と同じ
\w英数字とアンダースコア[a-zA-Z0-9_]と同じ
\s空白文字(スペース、タブ、改行など)

これらはほんの一部ですが、これだけでも多くのパターンを表現可能です。

よく使う正規表現パターン一覧

実務で頻繁に使われるJava正規表現のパターンをいくつか見てみましょう。

対象正規表現パターン例
半角数字のみ^[0-9]+$
半角英字のみ^[a-zA-Z]+$
郵便番号 (例: 123-4567)^\d{3}-\d{4}$
携帯電話番号 (例: 090-1234-5678)^0[7-9]0-\d{4}-\d{4}$
メールアドレス (簡易版)^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

これらのパターンはコピーしてすぐに使えますし、自分でカスタマイズする際の土台にもなります。

初心者が混乱しやすいエスケープのルール

Javaの正規表現で初心者が最もつまずきやすいのがエスケープ処理です。

正規表現のメタ文字(.* など)を、特別な意味ではなく「ただの文字」として扱いたい場合は、直前にバックスラッシュ\を置きます。例えば、IPアドレスの区切りである.を探したい場合は、\.と記述します。

ここからがJava特有のルールです。Javaの文字列リテラル内では、バックスラッシュ\自体も特殊文字として扱われるため、\を記述するには\\と2つ重ねる必要があります。

結果として、正規表現で\dと書きたいパターンは、Javaのコード内では"\\d"と書かなければなりません。この「二重のエスケープ」が、多くの混乱を招く原因です。

  • 正規表現の世界: \d (数字)
  • Javaコードの世界: "\\d"

このルールを最初にしっかり押さえておきましょう。

Javaでの正規表現の書き方と実装例

それでは、実際にJavaのコードで正規表現を使う方法を、サンプルコードを交えながら解説します。

Pattern.compile()とmatcher()の基本構文

Javaで正規表現を利用する際の基本的なコードの流れは、以下の3ステップです。

  1. Pattern.compile()メソッドで、正規表現パターンをコンパイルします。
  2. 生成したPatternオブジェクトのmatcher()メソッドに、検査したい文字列を渡してMatcherオブジェクトを取得します。
  3. Matcherオブジェクトのメソッドを使い、マッチング結果を処理します。
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample {
    public static void main(String[] args) {
        // 1. パターンをコンパイル
        String regex = "\\d{3}-\\d{4}"; // 郵便番号のパターン
        Pattern p = Pattern.compile(regex);

        // 2. Matcherオブジェクトを取得
        String target = "自宅の郵便番号は190-0014です。";
        Matcher m = p.matcher(target);

        // 3. マッチング処理
        if (m.find()) {
            System.out.println("郵便番号が見つかりました: " + m.group());
        } else {
            System.out.println("郵便番号は見つかりません。");
        }
    }
}

文字列を検索・抽出するサンプルコード

文章中から、パターンに一致する部分をすべて検索して抽出する例を見てみましょう。whileループとfind()メソッドを組み合わせるのが定石です。

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

public class FindAllExample {
    public static void main(String[] args) {
        String text = "連絡先は090-1111-2222か、もしくは080-3333-4444まで。";
        // 電話番号のパターン(ハイフン区切り)
        String regex = "0[7-9]0-\\d{4}-\\d{4}";

        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(text);

        System.out.println("文中から見つかった電話番号:");
        // find()で見つからなくなるまでループ
        while (m.find()) {
            // マッチした部分をgroup()で取得
            System.out.println(m.group());
        }
    }
}

このコードを実行すると、090-1111-2222080-3333-4444がそれぞれ出力されます。

マッチ結果を扱う方法(find()・group()など)

Matcherクラスには、マッチ結果を柔軟に扱うための便利なメソッドが用意されています。

  • find():対象文字列内に、パターンと一致する部分があるかどうかを検索します。見つかればtrueを返し、内部的にどの部分にマッチしたかを記録します。whileループで使うと、次の一致箇所へと検索を進めてくれます。
  • group():find()などで見つかった、直近のマッチ部分全体を文字列として返します。group(0)も同じ結果を返します。
  • group(int index):正規表現パターン内で()を使ってグループ化した部分を、インデックスで指定して取り出します。グループは左の(から順に1, 2, 3...と番号が振られます。
String text = "注文日は2023-10-26です。";
// 年、月、日をグループ化
String regex = "(\\d{4})-(\\d{2})-(\\d{2})";

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(text);

if (m.find()) {
    System.out.println("マッチ全体: " + m.group(0)); // 2023-10-26
    System.out.println("年: " + m.group(1));     // 2023
    System.out.println("月: " + m.group(2));     // 10
    System.out.println("日: " + m.group(3));     // 26
}

このようにグループ化を使うと、マッチした文字列をさらに分解して扱えるため、データ抽出の幅が広がります。

実践で使える!正規表現の応用例

ここからは、より実務に近い応用例を3つ紹介します。

メールアドレスの形式チェック

ユーザー登録フォームなどで必須となるメールアドレスのバリデーションです。matches()メソッドは、文字列全体がパターンに完全一致するかどうかを判定するため、形式チェックに適しています。

import java.util.regex.Pattern;

public class EmailValidator {
    public static void main(String[] args) {
        // 一般的なメールアドレスのパターン
        String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
        Pattern emailPattern = Pattern.compile(emailRegex);

        String validEmail = "test.user@example.com";
        String invalidEmail = "test-user-example.com";

        System.out.println(validEmail + " は正しい形式か? -> " +
                emailPattern.matcher(validEmail).matches());

        System.out.println(invalidEmail + " は正しい形式か? -> " +
                emailPattern.matcher(invalidEmail).matches());
    }
}

電話番号や郵便番号の検証

ハイフンの有無を許容するなど、少し柔軟なパターンを組む例です。?メタ文字を使えば「直前の文字が0回または1回出現する」ことを表現できます。

import java.util.regex.Pattern;

public class PhoneValidator {
    public static void main(String[] args) {
        // ハイフンの有無を許容する携帯電話番号のパターン
        String phoneRegex = "^0[7-9]0-?\\d{4}-?\\d{4}$";

        String phone1 = "090-1234-5678"; // ハイフンあり
        String phone2 = "08012345678";  // ハイフンなし(一部)
        String phone3 = "070-12345678"; // ハイフンあり(一部)

        System.out.println(phone1 + " -> " + Pattern.matches(phoneRegex, phone1));
        // このパターンだとphone2とphone3はfalseになる
        // より柔軟なパターンが必要
        
        // ハイフンをすべて任意にするパターン
        String flexiblePhoneRegex = "^0[7-9]0\\d{8}$|^0[7-9]0-\\d{4}-\\d{4}$";
        
        System.out.println("--- 柔軟なパターン ---");
        System.out.println(phone1 + " -> " + Pattern.matches(flexiblePhoneRegex, phone1));
        System.out.println("08012345678" + " -> " + Pattern.matches(flexiblePhoneRegex, "08012345678"));
    }
}

※単純な?だけでは不十分な場合、|(または)を使って複数のパターンを組み合わせることも有効です。

ログやHTMLから特定データを抽出する

大量のテキストから必要な情報だけを抜き出す、正規表現の真骨頂ともいえる使い方です。例えば、ApacheのアクセスログからIPアドレスとステータスコードを抽出する例を考えます。

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

public class LogParser {
    public static void main(String[] args) {
        String logLine = "192.168.1.1 - - [26/Oct/2023:10:00:00 +0900] \"GET /index.html HTTP/1.1\" 200 1024";
        // IPアドレスとステータスコードをグループ化して抽出
        String logRegex = "^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*?\\s(\\d{3})\\s";

        Pattern p = Pattern.compile(logRegex);
        Matcher m = p.matcher(logLine);

        if (m.find()) {
            System.out.println("IP Address: " + m.group(1));
            System.out.println("Status Code: " + m.group(2));
        }
    }
}

ただし、HTMLの解析はタグのネスト構造などが複雑なため、専用のパーサーライブラリ(Jsoupなど)を使う方が堅牢です。正規表現はあくまで簡易的な抽出手段と考えるのが良いでしょう。

Javaの正規表現でよくあるエラーと対処法

開発中に遭遇しやすいエラーとその対処法を知っておくことで、デバッグの時間を大幅に短縮できます。

エスケープ漏れによる「PatternSyntaxException」

PatternSyntaxExceptionは、正規表現の構文自体に誤りがある場合に発生する最も代表的な例外です。

原因の多くは、メタ文字( (, [ , * , + など)をただの文字として使いたいのに、エスケープ( \\ を前につける)を忘れていることです。

// エラーになる例: ( をエスケープしていない
// Pattern.compile("group(1)");

// 正しい例: ( と ) をエスケープしている
Pattern.compile("group\\(1\\)");

エラーメッセージに「Unclosed group」や「Dangling meta character」といったヒントが表示されるので、メッセージが指し示す箇所の構文を見直しましょう。

マッチしない原因をデバッグする方法

パターンが期待通りにマッチしない場合、以下の方法で原因を切り分けるのが効果的です。

  1. オンラインテスターを使う:Web上には多くの正規表現テストツールが存在します。Javaのコードに組み込む前に、まずはテスターサイトでパターンと対象文字列を試し、意図通りにマッチするか確認しましょう。これにより、問題がパターン自体にあるのか、Javaのコード実装にあるのかを切り分けられます。
  2. パターンを単純化する:複雑な正規表現がマッチしない場合、一度パターンを単純なものに分解して、どこまでが正しくマッチするかを部分的にテストします。少しずつ要素を付け足していくことで、問題の原因となっている箇所を特定しやすくなります。
  3. matches()とfind()を使い分けているか確認する:matches()は文字列全体がパターンに一致しないとfalseになります。一方、find()は文字列の一部でも一致すればtrueを返します。この違いを理解せず、意図しないメソッドを使っていないか確認しましょう。

パフォーマンスが低下する原因と改善策

正規表現は非常に強力ですが、書き方によっては著しくパフォーマンスが低下することがあります。

  • Patternオブジェクトの再利用:Pattern.compile()は、内部的にコストの高い処理を行っています。ループ処理の中で毎回compile()を呼び出すのは避け、ループの前に一度だけコンパイルし、生成したPatternオブジェクトを使い回すようにしましょう。
// 悪い例: ループ内で毎回コンパイル
for (String line : lines) {
    Pattern p = Pattern.compile("...");
    // ...
}

// 良い例: 事前にコンパイルして再利用
Pattern p = Pattern.compile("...");
for (String line : lines) {
    // ...
}
  • 破滅的なバックトラックを避ける:(a+)+ のように、入れ子になった量指定子(*や+)を含む複雑な正規表現は、「破滅的なバックトラック」と呼ばれる現象を引き起こし、処理が全く終わらなくなることがあります。できるだけシンプルなパターンを心がけることが重要です。

まとめ:Javaの正規表現を使いこなすコツ

最後に、Javaの正規表現をさらに深く理解し、有効に活用するための3つのコツを紹介します。

テストツールを活用しながら試す

正規表現の学習において、最も効果的なのは「試行錯誤」です。オンラインの正規表現テスターや、お使いのIDE(統合開発環境)のプラグインなどを活用し、リアルタイムで結果を確認しながら様々なパターンを試してみましょう。小さな成功体験を積み重ねることが、上達への一番の近道になります。

読みやすい正規表現を書くための工夫

正規表現は、一度作ると後から解読するのが非常に困難になることがあります。数か月後の自分や他の開発者のためにも、読みやすさを意識することは大切です。

複雑なパターンには、Pattern.COMMENTSフラグを使ってコメントを記述したり、処理を分割して単純なパターンの組み合わせにしたりする工夫が有効です。

実務で正規表現を使う際の注意点

正規表現は万能ではありません。あまりに複雑な正規表現は、可読性やメンテナンス性を大きく損ないます。時には、Stringクラスが提供するsubstring()indexOf()といった基本的なメソッドを組み合わせた方が、コードがシンプルで分かりやすくなる場合もあります。

また、外部からの入力を正規表現パターンに含める際は、意図しない処理を引き起こす「ReDoS攻撃」などのセキュリティリスクにも注意が必要です。ツールの特性を理解し、常に最適な手段を選択する視点を持つことが、優れたエンジニアへの一歩となるでしょう。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門