Javaで正規表現を扱うには java.util.regexパッケージを利用します。Javaでの正規表現の用途は 大別すると次の通りになります。
- ある文字列が正規表現パターンと一致するか、または正規表現パターンが含まれるかどうかの判定(一致判定)。また一致する文字列の抽出
- 正規表現パターンに一致する文字列を置換
- ある文字列を正規表現パターンで分割
Javaで正規表現を使うには 大きく分けて2通りの方法があります。
- 事前に正規表現パターンをコンパイルしておく方法
- 正規表現パターンをコンパイルせずに、検索・置換・分割を行う際に 正規表現パターンを指定する方法(ユーティリティ)
正規表現パターンによる一致判定等を行う前に、正規表現パターンのチェックをして 正規表現エンジンが解析できるようにするためのデータ構造を構築する処理を行います。同じ正規表現パターンで 繰り返し一致判定等を行う場合は この事前の処理が冗長となるため、事前処理をコンパイルという形で 独立して呼び出せるようになっています。
そのため 同じ正規表現パターンで何度も一致判定等を行う場合は、事前にコンパイルをした方が冗長な処理を省けます。一方で 同じ正規表現パターンを1度しか使わない場合は 事前にコンパイルしないユーティリティを使う方が便利です。
ただし、ユーティリティを使う場合は「大文字・小文字の区別をしない」とか「複数行モードを有効にする」といった 細かい制御を行うことができません。また 一致判定では 部分一致の判定ができず、抽出を行うこともできません。そのため 正規表現のフル機能を利用したい場合は、必然的に事前にコンパイルする方法を選択することになります。
事前にコンパイルをする使い方
主にPatternクラスとMatcherクラスを使います。Patternクラスで正規表現をコンパイルし、Matherクラスがマッチ操作を行い 適用結果を保持します。基本的な流れは次のようになります。
- 正規表現のパターンをコンパイルして Patternオブジェクトを作成します。
- Patternオブジェクトの正規表現に 適用したい文字列を渡して Matherオブジェクトを作成します。
- Matcherオブジェクトにて 一致判定・抽出・置換などを行います。
分割を行う場合は 1ステップ減って次のようになります。
- 正規表現のパターンをコンパイルして Patternオブジェクトを作成します。
- Patternオブジェクトに 分割したい文字列を渡して 分割を行います。
一致判定
Matherにて一致判定を行う場合、全体一致か部分一致かでメソッドが異なります。
- 正規表現パターンが 全体と一致するかどうか判定する場合には matches()メソッドを使います。
- パターンが 先頭から一致するかどうか判定する場合には lookingAt()メソッドを使います。
- パターンが どこかの部分と一致するかどうか判定する場合には find()メソッドを使います。
find()の場合は 複数箇所で一致する可能性がありますので、while文などのループと組み合わせて使うことができます。
matches()による一致判定の例です。一部分が一致していても、全体が一致していないと true にはなりません。
String message1 = "HD2100";
String message2 = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("[A-Z]+[0-9]+");
System.out.println(p.matcher(message1).matches()); // true
System.out.println(p.matcher(message2).matches()); // false 全体が一致しないと false
続いて lookingAt()による一致判定の例です。先頭が一致していないと trueにはなりません。
String message1 = "HD2100-000";
String message2 = "000-HD2100";
Pattern p = Pattern.compile("[A-Z]+[0-9]+");
System.out.println(p.matcher(message1).lookingAt()); // true
System.out.println(p.matcher(message2).lookingAt()); // false 先頭が一致しないと false
最後に find()による一致判定の例です。二度目のfind()の呼び出しでは 一度目で一致した部分の次から判定を開始します。一致するパターンが見つかる間はtrueを返します。一致するパターンがなくなるとfalseが返ります。
String message1 = "HD-2100";
String message2 = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("[A-Z]+[0-9]+");
System.out.println(p.matcher(message1).find()); // false
Matcher m = p.matcher(message2);
System.out.println(m.find()); // true "HD2100" と一致
System.out.println(m.find()); // true "MP1500" と一致
System.out.println(m.find()); // true "SD4000" と一致
System.out.println(m.find()); // false
抽出
一致箇所を抽出したい場合は、正規表現パターンを丸括弧 () で囲みます。Matcherのgroup()メソッドにより 一致箇所を抽出することができます。group()メソッドの引数で 正規表現パターンの丸括弧の位置(左からの出現順)を指定します。ここで注意する点は、位置の開始は1オリジンであることです。引数なしのgroup()と group(0) は一致したパターン全体を返します。
String message = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("([A-Z]+[0-9]+)\\s([A-Z]+[0-9]+)\\s([A-Z]+[0-9]+)");
Matcher m = p.matcher(message);
if(m.matches()) {
System.out.println(m.group()); // HD2100 MP1500 SD4000
System.out.println(m.groupCount()); // 3
for(int i = 1; i <= m.groupCount(); i ++) { // 位置の指定は 1 から
System.out.println(i + ":" + m.group(i)); // ex. 1:HD2100
}
}
丸括弧がネストされている場合は、左から数えて “(” が出現した順番が位置となります。例えば “((AB)((CD)(EF)))” というパターンの場合は 次のようになります。
String message = "ABCDEF";
Pattern p = Pattern.compile("((AB)((CD)(EF)))");
Matcher m = p.matcher(message);
if(m.matches()) {
for(int i = 1; i <= m.groupCount(); i ++) {
System.out.println(i + ":" + m.group(i));
}
}
実行結果は次のようになります。
1:ABCDEF
2:AB
3:CDEF
4:CD
5:EF
位置指定では 分かりづらく 間違いも発生し易いため、名前を付けて指定することもできます。正規表現パターンの中で “?<名前>” の形で名前付けをします。group()メソッドでは 正規表現パターンの中で付けた名前を指定します。存在しない名前を指定すると IllegalArgumentExceptionが発生します。
String message = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("(?<first>[A-Z]+[0-9]+)\\s(?<second>[A-Z]+[0-9]+)\\s(?<third>[A-Z]+[0-9]+)");
Matcher m = p.matcher(message);
if(m.matches()) {
System.out.println(m.group("first")); // HD2100
System.out.println(m.group("second")); // MP1500
System.out.println(m.group("third")); // SD4000
System.out.println(m.group("fourth")); // IllegalArgumentException 発生
}
lookingAt()やfind()を使った場合も 同様に group()メソッドで一致箇所を抽出することができます。find()を使った例を次に挙げます。
String message = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("([A-Z]+)([0-9]+)");
Matcher m = p.matcher(message);
while(m.find()) {
System.out.println(m.group()); // ex. HD2100
System.out.println(m.group(1) + "-" + m.group(2)); // ex. HD-2100
}
実行結果は次のようになります。
HD2100
HD-2100
MP1500
MP-1500
SD4000
SD-4000
置換
正規表現パターンで一致した箇所を置換するには MatcherクラスのreplaceFirst()、replaceAll()メソッドを使います。
String message = "HD2100 MP1500 SD4000";
Pattern p = Pattern.compile("[A-Z]+");
Matcher m = p.matcher(message);
System.out.println(m.replaceFirst("**")); // **2100 MP1500 SD4000
System.out.println(m.replaceAll("**")); // **2100 **1500 **4000
分割
正規表現パターンで指定した区切り文字で分割するには Patternオブジェクトのsplit()メソッドを使います。分割するにはMatcherを使わず Patternを使うことがポイントです。
String message = "HD2100;MP1500;SD4000";
Pattern p = Pattern.compile(";");
String[] array = p.split(message);
System.out.println(Arrays.toString(array)); // [HD2100, MP1500, SD4000]
また、配列ではなく Stringのストリームとして返すsplitAsStream()メソッドも用意されています。
String message = "HD2100;MP1500;SD4000";
Pattern p = Pattern.compile(";");
p.splitAsStream(message).forEach(System.out::println);
事前にコンパイルをしない使い方(ユーティリティ)
同じ正規表現パターンによる一致判定・置換・分割を1度しか行わない場合は、事前にコンパイルをしなくても呼び出せる ユーティリティメソッドを使うのが便利です。それぞれ 次のようなユーティリティが用意されています。
- 一致判定:Stringクラスのmatches()メソッド、Patternクラスのmatches()クラスメソッド
- 置換:StringクラスのreplaceAll()、replaceFirst()メソッド
- 分割:Stringクラスのsplit()メソッド
ただし、一致判定用のユーティリティのmatches()は 全体一致の判定を行うため部分一致には使えません。また 呼び出し側に 一致結果を抽出するためのMatcherオブジェクトが返らないため、抽出を行うことができません。更に、「大文字・小文字の区別をしない」とか「複数行モードを有効にする」といった細かい制御を指定することができません(これらのオプションは 事前にコンパイルをする際に Pattern.compile()の引数で指定するためです)。