PatternとMatcherによる正規表現処理

独特なPatternとMatcherの使い方をまとめました。

独特なPatternとMatcherの使い方をまとめました。

概要

Java標準ライブラリで正規表現処理を行う場合、「java.util.regex」パッケージのPatternとMatcherがベースになる。ただしユーティリティも用意されているので問題なければその方が楽。こちら

PatternとMatcherクラス図
Figure 1. PatternとMatcherクラス
クラス 概要

Pattern

コンパイル済みの正規表現

Matcher

Patternを特定の文字列に適用するときのステートを保持する

正規表現を表すPatternクラスとそれに対応した問い合わせの結果を保持するMatcherクラスを別々にすることで、概念的に分けている。概念的にはスッキリしているが使いやすいかはどうかは別。

Patternはイミュータブルであるが、MatcherはJDBCのResultSetのように内部でポインターを進ませる形で適用結果を取得していくので注意する。

基本手順としては次のような流れ。

  1. 正規表現パターンをコンパイルしてPatternオブジェクトを生成

  2. 正規表現を適用したいテキストを渡して、Matcherオブジェクト生成

  3. Matcherにより、検索・置換・分割などを行う

Pattern pattern = Pattern.compile("a*b");
Matcher matcher = pattern.matcher("aaaaab");

// マッチしているのでtrueが返る
boolean matchResult = matcher.matches();

Patternの生成

Patternの生成については次のことをおさえておきたい。

  • Pattern#compile()のファクトリから生成を行う。

    • 正規表現文字列を渡して、コンパイルされる。

    • 正規表現文字列が不正の場合はPatternSyntaxException。

  • 正規表現文字列の他にオプションを指定することができる。

    • ビット・マスクなので複数オプションは「|」演算で指定する。

  • Patternオブジェクトはイミュータブルであるため、マルチスレッドでも使い回し可能。

Pattern pattern =
    Pattern.compile("a*b", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
Table 1. Patternのオプション
オプション 説明

CASE_INSENSITIVE

ASCIIの大文字と小文字を無視する。

MULTILINE

複数行モードを有効にします。

DOTALL

「.」を全ての文字列とマッチさせる。

UNICODE_CASE

ASCII以外の大文字と小文字を無視する。

CANON_EQ

正規等価を有効にします。

UNIX_LINES

Unixライン・モードを有効にします。

LITERAL

パターンのリテラル構文解析を有効にします。

UNICODE_CHARACTER_CLASS

定義済みの文字クラスとPOSIX文字クラスのUnicodeバージョンを使用可能にします。

COMMENTS

パターン内で空白とコメントを使用できるようにします。

Matcherオブジェクトの生成と使い方

基本的には次のようなかたち。

String input = "cat dog cap";

Pattern pattern = Pattern.compile("ca.");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
  String matched = matcher.group();
  System.out.printf("[%s] がマッチしました。 Pattern:[%s] input:[%s]\n", matched, pattern, input);
}

出力

[cat] がマッチしました。 Pattern:[ca.] input:[cat dog cap]
[cap] がマッチしました。 Pattern:[ca.] input:[cat dog cap]

MatcherオブジェクトはPattern#matcher()メソッドに適用したい文字列(文章)を渡すことで生成できる。これで正規表現を適用した状況になり、その後は「抽出」「置換」といった処理を行うことになる。

MatcherオブジェクトはJDBCのResultSetのようなイメージ。正規表現の場合は複数回マッチされることが考えられるので、上記の例のようにwhile文などで回す。Matcherオブジェクトは内部でポインターを持っているので、find()がtrueを返すのと同時に内部ポインターが進み、次はその位置からfind()のマッチングを行う。そしてマッチしなくなったときにfalseが返る。

3種類の正規表現マッチメソッド

find()も含め次の3種類の正規表現マッチメソッドが用意されている。全てbooleanを返すがパターンの適用範囲がそれぞれ違う。

メソッド 概要

find()

パターンが(どこかの)部分とマッチした場合、true。

lookingAt()

パターンが先頭からマッチした場合、true。
(暗黙的に正規表現が「\A」が先頭に付加されて判定されるようなもの)

matches()

パターンが全体とマッチした場合、true。
(暗黙的に正規表現が「\A…\z」で囲まれて判定されるようなもの)

正規表現の検索処理を想像した場合find()の動きがデフォルトっぽく思えるが、lookingAt()やmatches()も場面によっては有効なので違いを把握して使う。

これらの3種類のメソッドはマッチしたモノがあったか否かをbooleanで返すだけなので、実際にマッチした文字列はMatcher#group()などから取得する。

String input = "http://example.com/";
Pattern[] patterns = new Pattern[] { Pattern.compile(".com"), Pattern.compile("http://"), Pattern.compile("http://\\w+.com/") };
for (Pattern pattern : patterns) {
  log.info("input[{}] pattern[{}]", input, pattern);
  log.info("find() result is {}", pattern.matcher(input).find());
  log.info("lookingAt() result is {}", pattern.matcher(input).lookingAt());
  log.info("matches() result is {}\n", pattern.matcher(input).matches());
}
出力
input[http://example.com/] pattern[.com]
find() result is true
lookingAt() result is false
matches() result is false

input[http://example.com/] pattern[http://]
find() result is true
lookingAt() result is true
matches() result is false

input[http://example.com/] pattern[http://\w+.com/]
find() result is true
lookingAt() result is true
matches() result is true

抽出

Matcher#group()メソッドでマッチした文字列を取得できる。

String input = "cat dog cap";

Pattern pattern = Pattern.compile("ca.");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
  String matched = matcher.group();
  System.out.printf("[%s] がマッチしました。 Pattern:[%s] input:[%s]\n", matched, pattern, input);
}
出力
[cat] が抽出されました。
[cap] が抽出されました。

置換

次の2つのメソッドが用意されている。置換された文字列が戻り値。

メソッド 概要

replaceFirst()

最初の部分シーケンスを指定された置換文字列に置き換える。

replaceAll()

指定された置換文字列に置き換える。

String input = "---dog---dog---dog---";
Pattern pattern = Pattern.compile("dog");
String replacement = "cat";

log.info("input[{}]  pattern[{}] replacement[{}]", input, pattern, replacement);
log.info("replaceFirst() replaced [{}]", pattern.matcher(input).replaceFirst(replacement));
log.info("  replaceAll() replaced [{}]", pattern.matcher(input).replaceAll(replacement));
出力
input[---dog---dog---dog---]  pattern[dog] replacement[cat]
replaceFirst() replaced [---cat---dog---dog---]
  replaceAll() replaced [---cat---cat---cat---]

分割

Pattern#split()を使ってString配列に分割できる。

String input = "one-two-three";
Pattern pattern = Pattern.compile("-");
log.info("input[{}]  pattern[{}]", input, pattern);
log.info("split() result is [{}]", Arrays.toString(pattern.split(input)));
出力
input[one-two-three]  pattern[-]
split() result is [[one, two, three]]

ユーティリティメソッドによる正規表現処理

正規表現処理で必ずしもPatternとMatcherを使う必要はない。Stringクラスを中心にの使いやすいユーティリティメソッドが用意されている。ただしPatternの使い回しや細かい処理は当然できないので、そういった場合はPatternとMatcherを使う。

メソッド 対応するユーティリティメソッド

Matcher#matches()

String#matches() or Pattern#matches()

Matcher#replaceAll()

String#replaceAll()

Matcher#replaceFirst()

String#replaceFirst()

Pattern#split()

String#split()

実装としては内部でPatternやMatcherが使われているのがほとんど。