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 にはなりません。
1 2 3 4 5 |
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にはなりません。
1 2 3 4 5 |
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が返ります。
1 2 3 4 5 6 7 8 9 10 |
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) は一致したパターン全体を返します。
1 2 3 4 5 6 7 8 9 10 |
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)))” というパターンの場合は 次のようになります。
1 2 3 4 5 6 7 8 |
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 2 3 4 5 |
1:ABCDEF 2:AB 3:CDEF 4:CD 5:EF |
位置指定では 分かりづらく 間違いも発生し易いため、名前を付けて指定することもできます。正規表現パターンの中で “?<名前>” の形で名前付けをします。group()メソッドでは 正規表現パターンの中で付けた名前を指定します。存在しない名前を指定すると IllegalArgumentExceptionが発生します。
1 2 3 4 5 6 7 8 9 |
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()を使った例を次に挙げます。
1 2 3 4 5 6 7 8 |
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 } |
実行結果は次のようになります。
1 2 3 4 5 6 |
HD2100 HD-2100 MP1500 MP-1500 SD4000 SD-4000 |
置換
正規表現パターンで一致した箇所を置換するには MatcherクラスのreplaceFirst()、replaceAll()メソッドを使います。
1 2 3 4 5 |
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を使うことがポイントです。
1 2 3 4 |
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()メソッドも用意されています。
1 2 3 |
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()の引数で指定するためです)。
「Pythonでの正規表現の使い方」(Qiita)
「PatternとMatcherによる正規表現処理」(Java 好き)