PerlやUNIXのgrepなどで欠かせない正規表現ですが、Javaでもこれを扱うためのパッケージjava.util.regexがJ2SE1.4からコアAPIに導入されました。ここでは、正規表現自体の詳細には触れず、クラスとそのメソッドの使い方についてのみ言及することにします。
まずは、具体的なサンプルを見てみましょう。サンプルは以下のような処理を行っています。
- 「javaまたはclassという拡張子が付いたファイル名を表す正規表現」と、入力文字列全体がマッチするかを「matches(マッチ)」によって調べる
- 入力文字列に正規表現にマッチする部分文字列があるかどうかを「find(検索)」により調べる
- 存在した場合には「group, start, endの各メソッドによってマッチした文字列情報を取り出す
- マッチした文字列を「replaceAll(置換)」により[file]に置換して出力する
正規表現による検索・置換を実行する(RegexTest.java)
import java.util.regex.*;
public class RegexTest{
public static void main(String[] args){
String regex = “\\b([A-Za-z_]\\w*)\\.(java|class)\\b”;
String[] seq = {
“Regex.java,2Regex.java, Regex.class”,
“_matches.java”};
Pattern p = Pattern.compile(regex); // Patternオブジェクトの生成
for(int i = 0; i < seq.length; i++){
Matcher m = p.matcher(seq[i]); // Matcherオブジェクトの生成
// マッチするかどうかを確かめる(matches)
System.out.println(“文字列全体が”+(m.matches()?”一致”:”不一致”));
m.reset(); // 検索を最初から開始するためにmをリセット
while(m.find()){ // 検索(find)し、マッチする部分文字列がある限り繰り返す
for(int j = 0; j <= m.groupCount(); j++){ // グループの個数繰り返す
// group, start, end各メソッドでマッチした文字列に関する情報を得る
System.out.println(m.group(j)+”, (”+m.start(j)+”-“+m.end(j)+”)”);
}
}
// 置換(replaceAll) を実行。resetの実行は不要
System.out.println(m.replaceAll(“[file]”)); //[file]に置換する
}
}
} |
文字列全体が不一致
Regex.java, (0-10)
0->Regex.java, (0-10)
1->Regex, (0-5)
2->java, (6-10)
Regex.class, (24-35)
0->Regex.class, (24-35)
1->Regex, (24-29)
2->class, (30-35)
[file],2Regex.java, [file]
文字列全体が一致
_matches.java, (0-13)
0->_matches, (0-8)
1->java, (9-13)
[file] |
Javaで正規表現を用いるためには2つのオブジェクトが必要です。
- 正規表現を表すオブジェクト(java.util.regex.Pattern オブジェクト)
- 入力シーケンス(マッチの対象となる文字列)を持ち、マッチを行うオブジェクト(java.util.regex.Matcher オブジェクト)
String型で表した正規表現を引数に取るPatternのクラスメソッドcompileによってPatternオブジェクトが生成されます。正規表現の文字面をString型で表す際、文字クラスなどに用いるバッククオートはバッククオートによってエスケープしなければなりません。
Matcher オブジェクトはPatternオブジェクトのmatcherメソッドによって生成されます。このMatcherオブジェクトから正規表現のマッチ・検索・置換などの各種メソッドを呼び出すことができます。
ここで必要な正規表現は、
\b[A-Za-z_]\w*\.(java|class)\b |
となります(\bは単語境界を、\wは単語構成文字([a-zA-Z_0-9]に同じ)を表す文字クラスです)。
この正規表現に対して、
Regex.java,2Regex.java,
Regex.class |
という入力シーケンスを与え、マッチを行います。
Matcherオブジェクトが持つメソッドは例えば以下のようなものがあります。
- マッチ:matches(戻り値/boolean)
matchesによって、入力シーケンス全体が正規表現に一致するかどうかを調べることができる
- 検索:find (戻り値/boolean)
入力シーケンスが正規表現にマッチする文字列を含むかどうかを調べることができる
- 置換:replaceAll (戻り値/String)
入力シーケンスを検索し、正規表現とマッチする部分を指定した文字列で置換し、その結果の文字列をString型として返す
マッチする部分が存在した場合、その部分の開始位置と終了位置がMatcherオブジェクトに保存されます。後でMatcherのインスタンスメソッドgroupでマッチした部分の文字列を取り出すことができ、 startとendにより開始/終了位置を調べることができます。
また、正規表現の一部を丸括弧()で囲うことでグループを複数指定することができますが、このグループはこれらのメソッドにint型の引数を与えて参照することができます。
上で示したソースプログラムでは、正規表現全体のうち、「拡張子を除いたファイル名」と「.(ドット)を除いた拡張子」に当たる正規表現がグループとして指定され参照されています。
表1 group、start、endの引数とそれに対応する文字列
|
引数 |
文字列 |
|
0 |
マッチした文字列全体 |
|
n(1以上の整数) |
左からn番目の開き括弧によって指定された部分シーケンスに対応する文字列 |
|
下表に、正規表現\b([A-Za-z_]\w*)\.(java|class)\bに対し文字列“Regex.java”を与えた際のgroupメソッドの戻り値を示しました。
表2 “Regex.java”に対するgroupの戻り値
|
groupによる指定 |
戻り値のString |
|
group(0) |
Regex.java |
|
group(1) |
Regex |
|
group(2) |
java |
|
while(m.find()){ // マッチする文字列を含む限り繰り返す
for(int i = 0; i <= m.groupCount(); i++){ // 指定したグループの数だけ繰り返す
System.out.println(m.group(i)+”, (”+m.start(i)+”,”+m.end(i)+”)”);
}
} |
なお、groupCountメソッドはグループの個数を返します。グループが指定されていないときは0を返しますが、group(0)は表1にあるようにマッチした文字列全体を返すので問題なく動きます。
findはいったんマッチしたら次に呼び出された時にはマッチした文字列の次の位置から検索を始めます(図1)。lookingAtというメソッドも検索に使うことができますが、こちらはいったんマッチしても、再度呼び出されたときには入力シーケンスの最初から検索を開始します。
図1 findによる検索の仕組み
最も基本的なクラスであるStringクラスでも、J2SE1.4で追加されたmatches、replaceAll、splitなどのメソッドにおいて正規表現を使えるようになりました。
String seq = "Regex.java,2Regex.java, Regex.class";
seq.matches("\\b[A-Za-z_]\\w*\\.(java|class)\\b ");
//=> false
seq.replaceAll("\\b[A-Za-z_]\\w*\\.(java|class)\\b","[file]");
//=> "[file],2Regex.java, [file]"
seq.split("\\s*,\\s*"); // 前後に0個以上の空白文字がある","(カンマ)
//=> String[]{"Regex.java", "2Regex.java", "Regex.class"} |
単純な処理のみ行うときには、上のようにStringのメソッドを用いて楽にコードを書くことができます。ただしもちろん正規表現の再利用などはできないので、場合に応じて使い分けてください。