Javaのmatches()が期待する動作にならない落とし穴
公開日
正規表現(せいきひょうげん、regular expression)とは、文字列のパターンマッチを行う機能です。
Java言語も文字列に対して正規表現でパターンマッチする仕掛けがありJava言語の場合、正規表現はPatternクラス、Matcherクラス、Stringクラスで利用可能です。
このうちいずれのクラスでも提供されているPattern.matches()、Matcher.matches()、String.matches()を使う場合は、他の言語を含め一般的な正規表現の動作と異なっており、これを踏まえた上でコーディングしないと期待する動作をしないため、はまりがちです。
これが以外と分かりにくい動作だったので、この記事にまとめたいと思います。
Contents
正規表現について
Java言語を含むプログラミング言語では一般的に部分文字列の検索とか文字列の置換といった操作で正規表現が利用されています。
正規表現は、ルールを表す表記方法であり、大抵はどのプログラミング言語であっても同様の表記方法をマスターしてしまえば、同じことを別のプログラミング言語でも実現できるし、別のプログラミング言語で書かれたプログラムを見ても内容を理解することが出来る、いわゆるデファクトスタンダード的なプログラム手法だとも言えます。
それゆえに、一度でも正規表現を身につけ、Webを検索することなく使えるようになると、コーディング効率を飛躍的に向上させることができます。
しかしながらJava言語のPattern.matches(), Matcher.maches(), String.matches()は、一般的な正規表現の動作と異なっており、使う場合はその動作を理解しないと、デバッグに時間をとられてしまいます。
Javaで正規表現を扱えるクラス
軽く検索してみると、Java言語で正規表現が使えるのは以下のクラスとのこと。
- Matcherクラスのfind()メソッドを使った方法
- Patternクラスのmatches()メソッドを使った方法
※matches()と同等のメソッドはMatcher.matches(),String.matches()にもあり
これらの正規表現について、Javaのドキュメントを確認しました。
Matcherクラスのfind()メソッドを使った方法
public boolean find()
入力シーケンスからこのパターンとマッチする次の部分シーケンスを検索します。
網掛けの部分という表現が今回の重要なポイントだったりします。
さらっとだけ書いてあるし、どちらかというとこっちが正規表現としてなじみのある動作なので見落としがちですが、find()では中間一致も出来るという仕様です。
念のため正確な表記を英語版を見て確認します。
public boolean find()
Attempts to find the next subsequence of the input sequence that matches the pattern.
subsequenceなので明白ですね。
Patternクラスのmatches()メソッドを使った方法
public static boolean matches(String regex, CharSequence input)
指定された正規表現をコンパイルして、指定された入力とその正規表現をマッチします。
どちらかというとこっちの動作を詳しく書いてほしいのですが、「部分」とつかない入力、つまりmatches()では正規表現が入力全体に完全にマッチするか確認するという動作になります。
なお、Pattern.matches()と同等のメソッドが次のメソッドに定義されてますが、同等機能ということで代表してPattern.matches()を使い確認したいと思います。
動作比較
確認対象の文字列「hello 123 world」のうち、検索する文字列として「123」を含むか判定するコードの違いを比較しました。
確認用ソースコード
確認用ソースコードとしては、前記した3種類の正規表現の使い方に加えて、次の2種類の動作を比較すると言う内容で次に示します。
- 検索する文字列が確認対象の文字列の中間のいずれかに一致させるための正規表現
- 検索する文字列が確認対象の文字列の全体一致させるための正規表現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
String str="hello 123 world"; //中間一致のための正規表現 String regex1 = "123"; //全体一致のための正規表現 String regex2 = "^.*123.*$"; // 1. 中間のいずれかに一致させるための正規表現 // (1) PatternおよびMatcher.find() Pattern p1 = Pattern.compile(regex1); Matcher m1 = p1.matcher(str); System.out.println("1 (1) Pattern + Matcher.find() : " + m1.find()); // (2) Pattern.matches() boolean matches1 = Pattern.matches(regex1, str); System.out.println("1 (2) Pattern.matches() : " + matches1); // 2. 全体一致させるための正規表現 // (1) PatternおよびMatcher.find() Pattern p2 = Pattern.compile(regex2); Matcher m2 = p2.matcher(str); System.out.println("2 (1) Pattern + Matcher.find() : " + m2.find()); // (2) Pattern.matches() boolean matches2 = Pattern.matches(regex2, str); System.out.println("2 (2) Pattern.matches() : " + matches2); |
実行結果
全体一致させるための正規表現は、どの使い方であってもマッチさせることが出来ましたが、
中間のいずれかに一致させるための正規表現であった場合、matches()はマッチさせることが出来ませんでした。
1 2 3 4 |
1 (1) Pattern + Matcher.find() : true 1 (2) Pattern.matches() : false 2 (1) Pattern + Matcher.find() : true 2 (2) Pattern.matches() : true |
最後に
いかがだったでしょうか?
matches()は、正規表現をそこそこ知っている人がパッとJavaドキュメントを見ただけだと、あたかもいつもの使い方が出来そうに勘違いしてしまいます。
また今回取り上げた問題の諸悪の根源はmatches()メソッドがbooleanを返却するというメソッドであって、間違ってコードを書いてしまっても判定結果がtrueとfalseの逆に返却されるだけなので、意外とデバッグに苦戦してしまいます。
自分自身、過去何度か同じことをやってしまった気がするので、いい加減同じことを繰り返したくなかったので、重い腰を上げメモとして残してみました。
レスポンシブ広告
関連記事
関連記事はありませんでした