S-JIS[2009-02-02/2019-12-08] 変更履歴

Javaの正規表現

Javaで正規表現を扱うには、java.util.regex.PatternクラスMatcherクラス)を使用する。(JDK1.4以降)
正規表現でどのような文字が使えるのか(正規表現構文)はPatternのJavadocに載っている。


Patternの使用例

matches()

文字列がパターン全体に完全にマッチするかどうかを判定する。

import java.util.regex.Pattern;
	String regexp = "ab.*ef";
	String test = "abcdef";
	boolean match = Pattern.matches(regexp, test);
	System.out.println(match);

↓実行結果

true

.」はどんな文字でもマッチする(1文字分。ただしデフォルトでは改行コードは含まない)。
「*」は直前の文字が0個以上という意味。
したがって「.*」は、どんな文字列にもマッチする。
それ以外のアルファベットはその文字そのもの。
したがって「ab.*ef」は、abで始まってefで終わる文字列にマッチする。
matches()メソッドは、文字列が正規表現にマッチした場合にtrueになる。

→内部ではMatcherクラスを使っている


split()

文字列をパターンによって分割する。[2014-04-15]
String#split()は、内部ではPattern#split()が使われている。

	Pattern pattern = Pattern.compile(":");
	String[] ss = pattern.split("a:bc:def", 0); //「:」で分割
	for(String s:ss){
		System.out.println(s);
	}

↓実行結果

a
bc
def

split()の第2引数limitは、何個に分割するかを指定する。
1を指定すると1個になる(つまり分割されない)。2を指定すると二分割。
0を指定すると、無制限に分割される。

第2引数の無いsplit()は、limitに0を指定したものと同じ。


splitAsStream()

split()と同様に文字列を分割するが、Stream<String>を返す。(JDK1.8)[2014-04-15]

import java.util.regex.Pattern;
import java.util.stream.Stream;
	Pattern pattern = Pattern.compile(":");
	Stream<String> stream = pattern.splitAsStream("a:bc:def");
	stream.forEach(System.out::println);

↓実行結果

a
bc
def

quote()

「正規表現で特別な意味を持つ文字」をエスケープしたパターン文字列を返す。(JDK1.5)[2014-04-15]

  エスケープ無し 手動エスケープ quoteメソッド使用
String[] ss = "a.b.c".split(".");
System.out.println(Arrays.toString(ss));
String[] ss = "a.b.c".split("\\.");
System.out.println(Arrays.toString(ss));
String[] ss = "a.b.c".split(Pattern.quote("."));
System.out.println(Arrays.toString(ss));
実行結果 [] [a, b, c] [a, b, c]

asPredicate()

パターンにマッチするものが1つでもあるかどうかを判定する関数を返す。(JDK1.8)[2014-04-15]

	Pattern pattern = Pattern.compile("123");
	Predicate<String> predicate = pattern.asPredicate();
	System.out.println(predicate.test("abc123def"));

↓実行結果

true

これは、以下の関数(ラムダ式)と同じ。

	Pattern pattern = Pattern.compile("123");
	Predicate<String> predicate = s -> pattern.matcher(s).find();

asMatchPredicate()

パターンに(完全に)マッチするかどうかを判定する関数を返す。(Java11)[2018-10-01]

	var pattern = Pattern.compile("abc");
	var predicate = pattern.asMatchPredicate();
	System.out.println(predicate.test("abc"));

↓実行結果

true

これは、以下の関数(ラムダ式)と同じ。

	var pattern = Pattern.compile("123");
	Predicate<String> predicate = s -> pattern.matcher(s).matches();

Matcherの使用例

matches()

Pattern#matches()は、内部ではMatcherクラスを使用している。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
	String regexp = "ab.*ef";
	String test = "abcdef";
	Pattern pt = Pattern.compile(regexp);
	Matcher m = pt.matcher(test);
	System.out.println(m.matches());

.」はどんな文字でもマッチするのだが、デフォルトでは改行コードにはマッチしない。
改行コードにもマッチさせたい場合は、Pattern#compile()の第2引数にDOTALLフラグを指定する。

	Matcher m;
	m = Pattern.compile("ab.*ef").matcher("abc\ndef");
	System.out.println(m.matches());
	→false

	m = Pattern.compile("ab.*ef", Pattern.DOTALL).matcher("abc\ndef");
	System.out.println(m.matches());
	→true

find()

matches()は文字列全体がパターンにマッチしているかどうか判断するものだが、find()では、文字列の一部がパターンにマッチしているかどうかを判定する。
(いわばStringのcontains()相当)

//「123」が含まれているかどうか
	Matcher m = Pattern.compile("123").matcher("abc123def123ghi");
	System.out.println(m.find());
	→true

find()によって一致する文字列が見つかった場合、Matcherはその位置を保持している。[2009-02-20]
find()によって複数の文字列がマッチする場合もあるが、find()を複数回呼ぶことでマッチした位置を全て取得することが出来る。

	Matcher m = Pattern.compile("123").matcher("abc123def123ghi");
	while (m.find()) {
		// 見つけた文字列と、その位置
		System.out.println(m.group() + ": " + m.start() + "/" + m.end());
//JDK1.5
//		MatchResult mr = m.toMatchResult();
//		System.out.printf("%s: %d/%d\n", mr.group(), mr.start(), mr.end());
	}
↓
123: 3/6
123: 9/12

※find()を呼ぶ度にMatcherインスタンス内のstart/endは変わっていくが、MatchResult(JDK1.5以降)は独立したインスタンスとなっているので変化しない。
(ただしMatchResultはstart/end以外のデータもコピーして保持するので、start/endしか使わないならちょっとコストが高いかも)


group()

find()で見つけた位置の文字列はgroup()で取得することが出来る。[2014-09-28]

	Matcher m = Pattern.compile("[0-9]+").matcher("abc123def456ghi");
	while (m.find()) {
		String s = m.group();
		System.out.println(s);
	}

また、パターンを丸括弧で囲むことにより、その部分を取得することが出来る。

	Matcher m = Pattern.compile("abc([0-9]+)def([0-9]+)(.*)").matcher("abc123def456ghi");
	if (m.matches()) {
		System.out.println(m.group(0));	//→abc123def456ghi
		System.out.println(m.groupCount());	//→3

		for (int i = 1; i <= m.groupCount(); i++) {
			String s = m.group(i);
			System.out.println(s);
		}
	}

↓実行結果

abc123def456ghi
3
123
456
ghi

group(0)はgroup()と同等で、マッチしたパターン全体を返す。
なので、丸括弧で囲んだ部分を表す添字は1から始まる。


find()の場合も丸括弧で囲んでおくと、その部分を取得することが出来る。

	String test = "abc123def456ghi";

	Pattern pattern = Pattern.compile("([a-z]+)([0-9]*)");
	Matcher m = pattern.matcher(test);
	while (m.find()) {
		System.out.println("group=" + m.group());
		System.out.println("count=" + m.groupCount());

		for (int i = 1; i <= m.groupCount(); i++) {
			System.out.println("[" + m.group(i) + "]");
		}
	}

↓実行結果

group=abc123
count=2
[abc]
[123]
group=def456
count=2
[def]
[456]
group=ghi
count=2
[ghi]
[]

なお、「*」でパターンを指定している箇所が0文字でマッチした場合は、空文字列が返ってくる。[2015-03-22]


JDK1.7で、groupメソッドに名前を指定できるようになった。[2015-03-22]
正規表現内に名前を指定しておくことで、その名前を使って値を取得できる。
start/endメソッドで名前が指定できるのは、JDK1.8から。[2015-06-14]

名前は、取得したい値の丸括弧内の先頭に「?<名前>」という形で指定する。
(名前に使えるのは英数字のみ(記号は使えない)[2019-12-08]

	String test = "abc123def456ghi";

	Pattern pattern = Pattern.compile("(?<alphabet>[a-z]+)(?<number>[0-9]*)");
	Matcher matcher = pattern.matcher(test);
	while (matcher.find()) {
		System.out.printf("[%s][%s]%n", matcher.group("alphabet"), matcher.group("number"));
	}

↓実行結果

[abc][123]
[def][456]
[ghi][]

start()・end()

find()でマッチした文字列の位置をstart(), end()で取得することが出来る。[2015-06-14]

	String test = "abc123def456ghi";

	Matcher m = Pattern.compile("[0-9]+").matcher(test);
	while (m.find()) {
		// 見つかった文字列
		String s = m.group();
		// 見つかった位置
		String t = test.substring(m.start(), m.end()); 

		System.out.println(s.equals(t));
	}

パターンを丸括弧で囲むことにより、その部分を取得することが出来る。

	String test = "abc123def456ghi";

	Pattern pattern = Pattern.compile("([a-z]+)([0-9]*)");
	Matcher m = pattern.matcher(test);
	while (m.find()) {
		System.out.println("group=" + m.group());
		System.out.println("count=" + m.groupCount());

		for (int i = 1; i <= m.groupCount(); i++) {
			System.out.println("[" + m.group(i) + "] " + m.start(i) + "/" + m.end(i));
		}
	}

↓実行結果

group=abc123
count=2
[abc] 0/3
[123] 3/6
group=def456
count=2
[def] 6/9
[456] 9/12
group=ghi
count=2
[ghi] 12/15
[] 15/15

JDK1.8で、start/endメソッドに名前を指定できるようになった。
groupメソッドでは、JDK1.7で名前を指定できるようになっていた)

	String test = "abc123def456ghi";

	Pattern pattern = Pattern.compile("(?<alphabet>[a-z]+)(?<number>[0-9]*)");
	Matcher matcher = pattern.matcher(test);
	while (matcher.find()) {
		System.out.printf("%s: %d/%d%n", matcher.group("alphabet"), matcher.start("alphabet"), matcher.end("alphabet"));
		System.out.printf("%s: %d/%d%n", matcher.group("number"), matcher.start("number"), matcher.end("number"));
	}

↓実行結果

abc: 0/3
123: 3/6
def: 6/9
456: 9/12
ghi: 12/15
: 15/15

replaceFirst()・replaceAll()

find()でマッチする文字列を別の文字列に変換することも出来る。
replaceFirst()ではマッチした最初の1文字列、replaceAll()はマッチする全ての文字列を置換する。

//「123」を「ZZZ」に置換する
	Matcher m = Pattern.compile("123").matcher("abc123def123ghi");
	System.out.println(m.replaceFirst("ZZZ"));
	System.out.println(m.replaceAll("ZZZ"));

↓実行結果

abcZZZdef123ghi
abcZZZdefZZZghi

String#split()

文字列を指定された文字で分割して配列を作るString#split()も、指定するのは正規表現。マッチした文字で区切られる。
内部ではPattern#split()が使われている。

//カンマで分割する
	String[] ss = "abc,def,ghi".split(",");
	System.out.println(Arrays.toString(ss));
//セミコロンで分割する
	String[] ss = "abc;def;ghi".split(";");
	System.out.println(Arrays.toString(ss));

 

//カンマ又はセミコロンで分割する
	String[] ss = "abc;def,ghi".split("[,;]");
	System.out.println(Arrays.toString(ss));

※[ ]でくくると、その中のどれか一文字にマッチする。
 

//ピリオドで分割する
	String[] ss = "abc.def.ghi".split("\\.");
	System.out.println(Arrays.toString(ss));

※ピリオド「.」やアスタリスク「*」は正規表現では特別な意味を持つ為、カンマやセミコロンの様にそのままでは使えない。「\」でエスケープする必要がある。
→JDK1.5以降ではPattern.quote()を使う方が便利。[2014-04-15]


正規表現の例

内容 コード例 結果例 説明
パスの途中の日付を取得する。
[2015-08-28]
static String getDate(String s) {
  Pattern pattern = Pattern.compile("D:/tmp/data/.*/(?<date>\\d{8})/zzz.*");

  Matcher m = pattern.matcher(s);
  if (m.matches()) {
    return m.group("date");
  }
  return null;
}
D:/tmp/data/hoge/20150828/zzz/

20150828
文字列内の数値8桁部分を抽出する。
\\d」は数字、「\\d{8}」は数字が8個。
マッチした場合、丸括弧部分の値がgroupメソッドで取得できる。
丸カッコ内に名前を付けているので、名前で取得できる。
IPアドレスを抽出する。
[2015-08-28]
static String getIpAddress(String s) {
  Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)");

  Matcher m = pattern.matcher(s);
  if (m.find()) {
    StringBuilder sb = new StringBuilder(16);
    for (int i = 1; i <= m.groupCount(); i++) {
      int n = Integer.parseInt(m.group(i));
      if (0 <= n && n <= 255) {
        if (sb.length() != 0) {
          sb.append('.');
        }
        sb.append(n);
      } else {
        return null;
      }
    }
    return sb.toString();
  }

  return null;
}
192.168.0.1

192.168.0.1
文字列内にあるIPアドレス部分を抽出する。
\\d」は数字、「\\d+」は数字が1個以上。
\\.」はピリオド。
マッチした場合、丸括弧部分の値がgroupメソッドで取得できる。
\\d+」でマッチしているから必ず数字なので、parseIntでエラーになる事は無い。
(IPアドレスの数字の範囲を正規表現で表現するのは困難なので、正規表現の外で判定している)
192.168.0.256

null
備考欄に192.168.0.1とか入れないで欲しい

192.168.0.1
複数の空白を1つに変換する。
[2015-08-28]
static String compactBlank(String s) {
  return s.replaceAll("\\s+", " ");
}
abc \t\r\ndef

abc def
文字列内の複数の空白(改行も含む)を1つの空白に置換する。
\\s」は空白文字、いわば「[ \f\t\r\n]」のようなもの。
\\s+」は空白文字が1個以上。

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま