文字列の正規表現(Pattern / Matcher) — テキスト検証
正規表現は「文字列がルールに合っているか」「欲しい部分だけ抜き出す」ための強力な道具。Javaでは java.util.regex の Pattern と Matcher が中核です。パターンをコンパイルして、対象文字列とマッチングするのが基本の流れです。
基本の流れと役割
- 役割の分離:
- Pattern: 正規表現の「ルール」をコンパイルして保持する。繰り返し使う場合は一度コンパイルして再利用すると効率的。
- Matcher: コンパイル済みパターンと対象文字列を組み合わせ、照合・検索・抽出を行う。
import java.util.regex.*;
Pattern p = Pattern.compile("[A-Za-z0-9_]+"); // 英数字+アンダースコア
Matcher m = p.matcher("user_123");
boolean ok = m.matches(); // 文字列全体がルールに一致?
System.out.println(ok); // true
JavaTip: 繰り返し使うパターンは、毎回 compile せずに再利用すると速い。
matches / find / lookingAt の違い(ここが最重要)
- matches(): 文字列「全体」がパターンに一致するかを判定。入力検証(メール・日付など)に最適。
- find(): 文字列の「一部」に一致する箇所を順次見つける。ログ解析や抽出に向く。
- lookingAt(): 文字列の「先頭から」一致するか(全体一致ではない)。先頭固定のトークンチェックに使う。
var s = "ID=abc123; more...";
var p = Pattern.compile("[a-z0-9]+");
// 全体一致(false)
System.out.println(p.matcher(s).matches());
// 先頭から一致(false:先頭は 'I')
System.out.println(p.matcher(s).lookingAt());
// 部分一致(true:'abc123' を見つける)
Matcher m = p.matcher(s);
if (m.find()) System.out.println(m.group()); // abc123
Javaこれを混同すると「なぜ合わない?」が起きがち。入力検証なら matches、抽出なら find を使うのが定石です。
文字列のエスケープ(“二重バックスラッシュ”の罠)
Javaの文字列リテラルは「バックスラッシュでエスケープ」されるので、正規表現で \d(数字)や \s(空白)を書きたい場合は、Java文字列では \\d や \\s と「2つ」必要です。
// 数字だけ(1文字以上)
Pattern p = Pattern.compile("\\d+"); // 正規表現の \d をJava文字列で表すと \\d
System.out.println(p.matcher("123").matches()); // true
System.out.println(p.matcher("12a").matches()); // false
Java覚え方: 「正規表現のバックスラッシュ」×「Java文字列のバックスラッシュ」= 2重に必要。
代表的な検証・抽出パターン
- 半角数字のみ:
Pattern.compile("\\d+"); // 1文字以上の数字 [株式会社ドライブライン](https://driveline.jp/media/programming-38/)
Java- ID(半角英数字+アンダースコア):
Pattern.compile("[A-Za-z0-9_]+"); // 1文字以上 [株式会社ドライブライン](https://driveline.jp/media/programming-38/)
Java- 日付 yyyy-MM-dd(ざっくり):
Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); // 年-月-日 [株式会社ドライブライン](https://driveline.jp/media/programming-38/)
Java- 簡易メール形式(最低限):
Pattern.compile("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); // ドメイン末尾2文字以上 [株式会社ドライブライン](https://driveline.jp/media/programming-38/)
Java実務では「完全なメール検証」は過剰になりがち。形式の最低限チェックに留め、ドメイン照会などは別手段で行うのが現実的です。
グループと抽出(find + group)
- グループ: 丸括弧
(...)で「取り出したい部分」をキャプチャ。group(0)は全体一致、group(1)以降が各グループ。 - find ループ: 複数箇所を順番に抽出できる。
var log = "user=alice id=42; user=bob id=7";
// user と id をセットで抜き出す
Pattern p = Pattern.compile("user=([a-z]+)\\s+id=(\\d+)");
Matcher m = p.matcher(log);
while (m.find()) {
String user = m.group(1); // 1番目のグループ
String id = m.group(2); // 2番目のグループ
System.out.println(user + " -> " + id);
}
Javamatches と find の違い、そして group での抽出が使いこなしの鍵です。
フラグとオプション(大文字小文字無視・複数行など)
- 大文字小文字無視:
Pattern.CASE_INSENSITIVE - 複数行モード:
Pattern.MULTILINE(^ と $ が行単位で機能) - ドットで改行も含める:
Pattern.DOTALL
Pattern p = Pattern.compile("^error: (.+)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Error: first line\nINFO: ok\nerror: second");
while (m.find()) System.out.println(m.group(1));
Java用途に応じてフラグを組み合わせると、意図通りにマッチングしやすくなります。
実務のパフォーマンスと安全性の注意点
- パターンは再利用: 頻繁に使う場合、毎回
compileせずにキャッシュ・再利用すると速い(コンパイルコスト削減)。 - コメントモード活用: 複雑なパターンは「見通し」を優先。
Pattern.COMMENTSでスペースや#コメントを許可すると保守性が上がる。 - ReDoSに注意: 一部の正規表現は最悪ケースで爆発的に遅くなる(バックトラッキング過多)。入力サイズやパターン設計に注意し、アンカー使用・曖昧な量指定の抑制などで被害を避ける。
例題で身につける
例題1: 入力検証(郵便番号 123-4567)
var p = Pattern.compile("\\d{3}-\\d{4}");
System.out.println(p.matcher("135-0063").matches()); // true
System.out.println(p.matcher("1350063").matches()); // false
Java検証は「全体一致」の matches() を使うのが基本です。
例題2: ログからIPを抽出
var log = "from 192.168.1.2 to 10.0.0.5; bad 999.999.999.999";
var ip = Pattern.compile("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"); // 簡易版
var m = ip.matcher(log);
while (m.find()) System.out.println(m.group());
Java抽出は find() の反復で「部分一致」を順に拾います。
例題3: 大文字小文字を無視してタグ抽出
var html = "<Title>Hello</title><TITLE>World</TITLE>";
var p = Pattern.compile("<title>(.*?)</title>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
var m = p.matcher(html);
while (m.find()) System.out.println(m.group(1)); // Hello / World
JavaCASE_INSENSITIVE と DOTALL の組み合わせで、タグや改行をまたぐ抽出が楽になります。
すぐ使えるテンプレート
- 全体一致の検証(メール・日付など)
boolean valid = Pattern.compile("正規表現").matcher(input).matches();
Java- 部分一致の抽出(複数拾う)
Matcher m = Pattern.compile("(...)(...)").matcher(text);
while (m.find()) {
String g1 = m.group(1);
String g2 = m.group(2);
}
Java- パターン再利用(効率化)
final Pattern P_NUM = Pattern.compile("\\d+"); // static final でキャッシュ [株式会社ドライブライン](https://driveline.jp/media/programming-38/)
Java- フラグ指定
Pattern p = Pattern.compile("...", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Javaまとめ
- Pattern がルール、Matcher が照合役。使い分けは matches(全体)・find(部分)・lookingAt(先頭)。
- Java文字列のエスケープを忘れない(\d, \s などは二重バックスラッシュ)。
- 実務ではパターン再利用・コメントモード・ReDoS対策で「速く安全に」。
