Java で正規表現を使う方法を色々メモ。
String クラスの正規表現を使うメソッド
String
には正規表現を受け取るメソッドがいくつか存在する。
- matches(String)
- replaceAll(String, String)
- replaceFirst(String, String)
- split(String, int)
- split(String)
matches(String)
package sample.regexp;
public class Main {
public static void main(String[] args) {
String text = "abc123";
System.out.println(text.matches("[a-z0-9]+"));
System.out.println(text.matches("[a-z]+"));
}
}
true
false
- 文字列が指定した正規表現と完全に一致することを検証する
- 一部が一致するだけの場合は
false
になる
replaceAll(String, String)
package sample.regexp;
public class Main {
public static void main(String[] args) {
String text = "abc123";
System.out.println(text.replaceAll("[a-z]", "*"));
}
}
***123
- 第一引数に正規表現を渡し、マッチする部分を全て第二引数の文字列に置換する
置換文字列にマッチしたグループを使用する
package sample.regexp;
public class Main {
public static void main(String[] args) {
String text = "<<abc123>>";
System.out.println(text.replaceAll("([a-z]+)([0-9]+)", "$0, $1, $2"));
}
}
<<abc123, abc, 123>>
- 置換文字列に
$n
を含めることで、マッチしたグループを置換後の文字列再利用できる-
n
は0
はじまり -
0
はマッチした文字列全体を指している-
([a-z]+)([0-9]+)
にマッチした部分なので、abc123
が対象になる
-
-
1
から先は、()
で囲ったグループを順番に参照できる-
$1
は([a-z]+)
にマッチしたabc
が、 -
$2
は([0-9]+)
にマッチした123
が対象になる
-
-
- マッチしているグループの数より大きい数を
n
に指定した場合は、IndexOutOfBoundsException
がスローされる - 単純に
$
という文字列に置き換えたい場合はバックスラッシュ (\
) でエスケープするtext.replaceAll("[a-z]+", "\\$")
- エスケープしていない場合は、
IllegalArgumentException
がスローされる
- グループはインデックス以外にも名前を付けて名前で参照することも可能
- 詳細は こちら を参照
replaceFirst(String, String)
package sample.regexp;
public class Main {
public static void main(String[] args) {
String text = "abc123";
System.out.println(text.replaceFirst("[a-z]", "*"));
}
}
*bc123
- 正規表現にマッチした部分文字列のうち、最初にマッチした箇所だけを置換する
-
$n
で部分文字列を参照できるのは、replaceAll()
と同じ
split(String, int)
package sample.regexp;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String text = "a1b2";
for (int i=-1; i<5; i++) {
String[] elements = text.split("[0-9]", i);
System.out.println("limit=" + i + ",\telements=" + Arrays.toString(elements));
}
}
}
limit=-1, elements=[a, b, ]
limit=0, elements=[a, b]
limit=1, elements=[a1b2]
limit=2, elements=[a, b2]
limit=3, elements=[a, b, ]
limit=4, elements=[a, b, ]
- 第一引数で指定した正規表現にマッチする場所で文字列を分割する
- 第二引数の
limit
は、戻り値の配列のサイズの上限を決める-
limit
に1
以上の値を指定した場合、マッチした部分文字列の分割はlimit - 1
番目までになる -
limit==1
の場合は、limit - 1 => 0
になるので、分割は行われない(結果、配列のサイズは1
になる) -
limit==2
の場合は、limit - 1 => 1
になるので、正規表現[0-9]
に最初にマッチしたa1b2
の1
の部分で分割が行われ、そこで分割は終了する(結果、配列のサイズは2
になる) -
limit
に0
以下の値を指定した場合は制限無しの扱いになり、文字列の末尾まで分割が実行される- ただし、分割の結果末尾に文字列が残っていない(空白になった)ときの挙動が、
0
と負数とで異なる - 負数の場合は、最後の空白も配列の要素として残される
-
0
の場合は、最後の空白は破棄される
- ただし、分割の結果末尾に文字列が残っていない(空白になった)ときの挙動が、
-
分割の結果先頭が空白になる場合は、そのまま空白が配列の要素として設定される。
package sample.regexp;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String text = "0a1b2";
String[] elements = text.split("[0-9]", 0);
System.out.println(Arrays.toString(elements));
}
}
[, a, b]
split(String)
これは、 split(String, int)
の第二引数を 0
にしたのと同じ挙動になる。
Pattern クラス
String のメソッドとの違い
一部の例外1を除いて、 String
クラスの正規表現を使ったメソッドは、裏では Pattern
クラスに処理を委譲している。
たとえば、 replaceAll()
メソッドの実装を確認すると次のようになっている。
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Java における正規表現の処理は、この Pattern
クラス(と Matcher
)が担当している。
Pattern
クラスは compile()
で渡された文字列を正規表現として解釈する。
使用する正規表現が固定なのであれば、この compile()
は最初の1回だけ実行して、あとは Pattern
インスタンスを使いまわしたほうが効率がいい。
(Pattern
クラスはイミュータブルなので、マルチスレッドでも安全に使いまわすことができる)
しかし、 String
クラスの正規表現を使うメソッドを利用していると、この compile()
が毎回実行されてしまう。
そのため、固定の正規表現を何度も繰り返し実行するような場合に String
のメソッドを利用していると、 Pattern
インスタンスを使いまわすよりも処理速度が遅くなる。
public class Hoge {
// コンパイル済みの Pattern インスタンスを使いまわす
private static final Pattern HOGE_PATTERN = Pattern.compile("[0-9]+");
public boolean test(String text) {
return HOGE_PATTERN.matcher(text).matches(); // 動きは text.maches("[0-9]+") と同じ
}
}
基本的な使い方
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[0-9]+");
Matcher abc = pattern.matcher("123abc");
System.out.println(abc.matches());
Matcher _123 = pattern.matcher("123");
System.out.println(_123.matches());
}
}
false
true
- まず、
Pattern.compile(String)
で正規表現をコンパイルし、Pattern
インスタンスを取得する - つづいて
Pattern.matcher(String)
メソッドで検証したい文字列(入力シーケンス)を渡し、Matcher
インスタンスを取得する - 取得した
Matcher
インスタンスを使って、マッチしたかどうかなどの検証を行う -
Matcher.matches()
は入力シーケンス全体が正規表現と一致するかどうかを検証し、boolean
で結果を返す
分割
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.Arrays;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[a-z]+");
String[] elements = pattern.split("123abc456def789ghi");
System.out.println(Arrays.toString(elements));
elements = pattern.split("123abc456def789ghi", -1);
System.out.println(Arrays.toString(elements));
}
}
[123, 456, 789]
[123, 456, 789, ]
-
Pattern.split(String)
で、指定した文字列のうち正規表現にマッチする部分で文字列を分割する - 動きは
String.split(String)
,String.split(String, int)
と同じ
Matcher
-
Pattern
は正規表現を解釈するクラスで、次のような処理はMatcher
が行う- 入力シーケンスが正規表現にマッチしているかどうか
- マッチした部分の抽出
- マッチした部分の置換
-
Matcher
は、大きく次のようなステップで使用する- マッチ操作を実行する
- マッチ操作の結果を問い合わせる
- 必要であれば、 1, 2 を繰り返す
- マッチ操作の結果は、以下のメソッドで参照するできる
-
start()
マッチした入力シーケンス上の開始インデックス -
end()
マッチした入力シーケンス上の終了インデックス + 1 -
group()
マッチした部分文字列 - マッチ操作を実行していない状態でこれらのメソッドを実行すると、
IllegalStateException
がスローされる
-
-
Matcher
はスレッドセーフではないので注意
マッチ操作
Matcher
には、3つのマッチ操作が存在する。
-
matches()
- 入力シーケンス全体と正規表現がマッチするか検証する
-
lookingAt()
- 入力シーケンスの先頭から正規表現がマッチするか検証する
-
find()
- 入力シーケンスに正規表現にマッチする部分が存在するか順番に検証する
matches()
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
test("abc");
test("abc123");
}
private static void test(String text) {
Pattern pattern = Pattern.compile("[a-z]+");
Matcher matcher = pattern.matcher(text);
System.out.println("[text=" + text + "]");
if (matcher.matches()) {
System.out.println("matches = true");
System.out.println("start = " + matcher.start());
System.out.println("end = " + matcher.end());
System.out.println("group = " + matcher.group());
} else {
System.out.println("matches = false");
}
}
}
[text=abc]
matches = true
start = 0
end = 3
group = abc
[text=abc123]
matches = false
-
matches()
は、入力シーケンス全体が正規表現とマッチするか検証する - マッチする場合は
true
を返す
lookingAt()
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
test("abc");
test("123abc");
test("ab12");
}
private static void test(String text) {
Pattern pattern = Pattern.compile("[a-z]+");
Matcher matcher = pattern.matcher(text);
System.out.println("[text=" + text + "]");
if (matcher.lookingAt()) {
System.out.println("lookingAt = true");
System.out.println("start = " + matcher.start());
System.out.println("end = " + matcher.end());
System.out.println("group = " + matcher.group());
} else {
System.out.println("lookingAt = false");
}
}
}
[text=abc]
lookingAt = true
start = 0
end = 3
group = abc
[text=123abc]
lookingAt = false
[text=ab12]
lookingAt = true
start = 0
end = 2
group = ab
-
lookingAt()
は、入力シーケンスの先頭から正規表現にマッチするか検証する - 先頭からの検証結果がマッチしていれば、
true
が返される(全体が一致している必要はない)
find()
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
test("abc");
test("123abc456def789");
}
private static void test(String text) {
Pattern pattern = Pattern.compile("[a-z]+");
Matcher matcher = pattern.matcher(text);
System.out.println("[text=" + text + "]");
while (matcher.find()) {
System.out.println("start = " + matcher.start());
System.out.println("end = " + matcher.end());
System.out.println("group = " + matcher.group());
}
}
}
[text=abc]
start = 0
end = 3
group = abc
[text=123abc456def789]
start = 3
end = 6
group = abc
start = 9
end = 12
group = def
-
find()
メソッドは、入力シーケンスの先頭から正規表現にマッチする部分がないか走査する - マッチする部分文字列が存在した場合は
true
を返す -
find()
をもう一度実行すると、前回マッチした部分から再びマッチする部分文字列が存在しないか走査が行われる- 繰り返し実行することでマッチした部分文字列の抽出が可能
-
start()
,end()
,group()
は、直前にマッチした結果が返る
置換
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[a-z]+");
Matcher matcher = pattern.matcher("abc123def");
System.out.println("replaceAll = " + matcher.replaceAll("*"));
System.out.println("replaceFirst = " + matcher.replaceFirst("*"));
}
}
replaceAll = *123*
replaceFirst = *123def
-
Matcher.replaceAll(String)
で、マッチした部分文字列を全て置換する -
Matcher.replaceFirst(String)
で、最初にマッチした部分文字列だけを置換する
グループ
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("([a-z]+)([0-9]+)");
Matcher matcher = pattern.matcher("abc123de45fg");
int groupCount = matcher.groupCount();
System.out.println("groupCount=" + groupCount);
while (matcher.find()) {
System.out.println("==========");
String group = matcher.group();
System.out.println("group=" + group);
for (int i=0; i<=groupCount; i++) {
String g = matcher.group(i);
System.out.println("group(" + i + ")=" + g);
}
}
}
}
groupCount=2
==========
group=abc123
group(0)=abc123
group(1)=abc
group(2)=123
==========
group=de45
group(0)=de45
group(1)=de
group(2)=45
- 正規表現で定義したグループ(
()
で囲われた部分)について参照するには、以下のメソッドが用意されている-
groupCount()
正規表現で定義されたグループの数を取得する -
group()
直近のマッチ操作でマッチした文字列全体を取得する -
group(int)
直近のマッチ操作でマッチしたグループのうち、指定したインデックスのグループを取得する- 番号
0
はマッチした文字列全体なので、group()
と同じ結果を返す -
1
から先がマッチした部分文字列になる
- 番号
-
グループに名前をつける
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("(?<alphabets>[a-z]+)(?<numbers>[0-9]+)");
Matcher matcher = pattern.matcher("abc123de45fg");
while (matcher.find()) {
System.out.println("==========");
System.out.println("group(alphabets)=" + matcher.group("alphabets"));
System.out.println("group(numbers)=" + matcher.group("numbers"));
}
}
}
==========
group(alphabets)=abc
group(numbers)=123
==========
group(alphabets)=de
group(numbers)=45
- グループを
(?<グループ名>パターン)
と定義することで、グループに名前を定義することができる -
group(String)
メソッドで定義した名前を指定してグループにマッチした部分文字列を取得できる
グループ名を置換文字列で参照する場合は、次のようにする
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("(?<alphabets>[a-z]+)(?<numbers>[0-9]+)");
Matcher matcher = pattern.matcher("abc123def456");
String replaced = matcher.replaceAll("${numbers}${alphabets}");
System.out.println(replaced);
}
}
123abc456def
-
${グループ名}
でグループを参照できる
フラグ
-
Pattern
インスタンスを生成するさい、正規表現を解釈する方法をフラグで調整することができる。
Pattern pattern = Pattern.compile("[a-z]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
- フラグは
compile(String, int)
の第二引数で指定する - 指定できるのは、
Pattern
クラスにstatic
で宣言された定数になる - ビットマスクになっているので、複数のフラグを指定する場合は
|
で連結して指定する
CASE_INSENSITIVE (大文字小文字を区別しない)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[a-z]+", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("ABC");
System.out.println(matcher.matches());
}
}
true
-
CASE_INSENSITIVE
を指定した場合、マッチは大文字小文字を区別せずに行われる - 区別しなくなるのは US-ASCII の文字のみ
UNICODE_CASE(Unicode で大文字小文字を区別しない)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[a-zA-Z]+", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Matcher matcher = pattern.matcher("ABCabc");
System.out.println(matcher.matches());
}
}
true
-
UNICODE_CASE
とCASE_INSENSITIVE
を組み合わせると、 Unicode で大文字小文字の区別なしでマッチングが行われる
LITERAL(正規表現のメタ文字やエスケープ文字を使わない)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
Pattern pattern = Pattern.compile("[a-z]+", Pattern.LITERAL);
Matcher matcher = pattern.matcher("abc");
System.out.println(matcher.matches());
matcher = pattern.matcher("[a-z]+");
System.out.println(matcher.matches());
}
}
false
true
-
LITERAL
を指定すると、compile(String, int)
の第一引数で渡した文字列は単純な文字列として処理される -
[]
や+
のような正規表現で意味のある文字は、単純にその文字そのものとして解釈される
MULTILINE (複数行の文字列を処理する)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
test("[default]", () -> Pattern.compile("^[a-z]+$"));
test("[MULTILINE]", () -> Pattern.compile("^[a-z]+$", Pattern.MULTILINE));
}
private static void test(String label, Supplier<Pattern> patternSupplier) {
System.out.println(label);
Pattern pattern = patternSupplier.get();
String text = "abc\n"
+ "def\n";
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String group = matcher.group();
System.out.println(group);
}
}
}
[default]
[MULTILINE]
abc
def
-
MULTILINE
を指定した場合、行頭、行末を表す^
,$
の扱いが変化する - 何も指定していない場合、
^
,$
は純粋に文字列の先頭・末尾にのみマッチする -
MULTILINE
を指定している場合、改行で区切られたそれぞれが文字列として処理されるため、^
と$
は各行の先頭と末尾にマッチするようになる
COMMENTS (コメントを書けるようにする)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
String regexp = "# この行はコメントとして無視される\n"
+ " [a-z]+ ";
Pattern pattern = Pattern.compile(regexp, Pattern.COMMENTS);
Matcher matcher = pattern.matcher("abc");
System.out.println(matcher.matches());
}
}
true
-
COMMENTS
を指定すると、以下の文字列はコメント扱いになり無視される-
#
から行末まで - 空白スペース
-
DOTALL (.
で行末にもマッチするようにする)
package sample.regexp;
import org.openjdk.jmh.runner.RunnerException;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws RunnerException {
test("[default1]", () -> Pattern.compile(".+"));
test("[default2]", () -> Pattern.compile(".+$"));
test("[DOTALL]", () -> Pattern.compile(".+", Pattern.DOTALL));
}
private static void test(String label, Supplier<Pattern> patternSupplier) {
System.out.println(label);
Pattern pattern = patternSupplier.get();
String text = "abc\n"
+ "def\n";
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
String group = matcher.group();
System.out.println(group);
}
}
}
[default1]
abc
[default2]
def
[DOTALL]
abc
def
-
DOTALL
を指定すると、.
が行末にもマッチするようになる - デフォルトの場合、
.
は行末にはマッチしない
参考
- Pattern (Java Platform SE 8 )
- Matcher (Java Platform SE 8 )
- Unicodeに準拠した大文字と小文字を区別しない(UNIX_LINES, (?u)) - オプション修飾子 - Java正規表現の使い方
- パターンのリテラル構文解析を有効にする(LITERAL) - オプション修飾子 - Java正規表現の使い方
-
例えば
split(String regexp)
メソッドは、regexp
が正規表現のメタ文字などが使われていない素の文字列だった場合に、Pattern
を使わずに分割処理を行っている ↩