疲れたらやすむ

Javaを学ぶ上でハマったところを書いていきます。iPhoneアプリ開発や日常ネタもあるかも。

【Java】いつも悩む正規表現

今回は正規表現についての記事になります。

たまに使う機会があるのですが、毎回1時間ほど試行錯誤してしまうのでメモ的な意味も含めて。
ちゃんと理解しないと意外と扱いが難しいと感じます。

正規表現とは

説明しなくてもご存知だとは思いますが。

正規表現とは文字列のパターンを表現したものです。

何となく意味は伝わりますかね。
言葉よりも実物をお見せした方が早いかもしれません。

例えば、0〜9の数字1文字のパターンを正規表現で表すと[0-9]となります。

上記の正規表現では、例えばJavaで言う所の

String s1 = "1"
String s2 = "123"

など文字列の中に含まれている数字1文字ごとにマッチします。
文字列の中の数字は何がありますか?との問いに対する答えみたいな感じですね。
s1は1だけ、s2は1と2と3があります。それぞれ[0-9]の正規表現にマッチします。

Javaの場合は正規表現を扱う場面がいくつかあります。
単純に文字列の中にマッチする文字が含まれているか、もしくは文字列全体がマッチするか。
そして、正規表現に一致する文字列だけを置換したい場合など様々。

まずは用途別に見ていきましょう。

正規表現と一致する文字列が含まれているか

PatternとMatcherを用いて判定します。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s1 = "1";
		String s2 = "ABC9";
		String s3 = "ABCDE";

		Pattern pattern = Pattern.compile("[0-9]");

		Matcher matcher1 = pattern.matcher(s1);
		Matcher matcher2 = pattern.matcher(s2);
		Matcher matcher3 = pattern.matcher(s3);

		System.out.println(matcher1.find()); // true
		System.out.println(matcher2.find()); // true
		System.out.println(matcher3.find()); // false
	}
}

Patternクラスで正規表現を定義します。
次に、正規表現を定義したPatternクラスのmathcerメソッドの引数に判定を行う文字列を渡しMatcherを生成します。
生成したMatcherのfind()メソッドでは、文字列が正規表現を含んでいればtrueを返します。

文字列が正規表現とすべて一致するか

こちらもPatternとMatcherを用います。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s1 = "1";
		String s2 = "ABC9";
		String s3 = "ABCDE";
		String s4 = "123";

		Pattern pattern = Pattern.compile("[0-9]");

		Matcher matcher1 = pattern.matcher(s1);
		Matcher matcher2 = pattern.matcher(s2);
		Matcher matcher3 = pattern.matcher(s3);
		Matcher matcher4 = pattern.matcher(s4);

		System.out.println(matcher1.matches()); // true
		System.out.println(matcher2.matches()); // false
		System.out.println(matcher3.matches()); // false
		System.out.println(matcher4.matches()); // false
	}
}

Matcherクラスのmatchesメソッドで判定します。
[0-9]は0~9までの数字1文字に合致する正規表現なので、123は合致しません。
123に合致させたい場合は、例えば

Pattern pattern = Pattern.compile("[0-9]*");

としてあげると合致します。
ただし012などの0から始まる数字のみの文字列も合致してしまうので使用用途には注意。
この辺はいつも悩んじゃいます。

そして、PatternとMatcherを使わない方法も存在します。
こっちの方が簡単ですね。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123";		
		System.out.println(s.matches("[0-9]*")); // true
	}
}

これだけです。

どっちが良いかはお好みですかね。
あとは、業務の場合はコーディング規約とか他のソースでどうやっているかで使い分けると良いと思います。

正規表現に一致した部分を取り出す

文字列の中で正規表現と一致するものを取り出す方法です。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s = "123";

		Pattern pattern = Pattern.compile("[0-9]");
		Matcher matcher = pattern.matcher(s);

		while (matcher.find()) {
			System.out.println(matcher.group());
		}
	}
}

実行結果

1
2
3

findメソッドで一致する部分だけループさせ、groupメソッドで取り出します。

正規表現と一致する箇所を置換する

一致した箇所だけを他の文字に置換させます。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123ABC";
		System.out.println(s.replaceAll("[0-9]", "★"));
	}
}

実行結果

★★★ABC

StringのreplaceAllメソッドを使用します。
第1引数に正規表現、第2引数に置換後の文字を指定します。


ちなみにreplaceFirstメソッドで同じことをすると。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "ABC123";
		System.out.println(s.replaceFirst("[0-9]", "★"));
	}
}

実行結果

ABC★23

正規表現と一番最初に一致した箇所が置換されます。

replaceAllやreplaceFirstは、Matcherのfindメソッドと同等の条件となります。
そのため1文字ごとに正規表現と一致するか判定し、一致している場合は置換する動きをします。
もし、123を★に置換したいのであれば

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123ABC";
		System.out.println(s.replaceAll("[0-9]+", "★"));
	}
}

実行結果

★ABC

こんな感じの正規表現になります。
+は1回以上の繰り返し、*は0回以上の繰り返しを意味しています。
[0-9]*でも良いのでは?と思っていたのですが、試してみるとAとBの間などにも★が入っちゃいました。
0回以上なので、文字の間も合致しちゃうんですかね。

個人的正規表現まとめ

今まで苦労した正規表現をいくつかまとめておきます。

1から100までの数値
[1-9]|[1-9][0-9]|100

1から350まで
[1-9]|[1-9][0-9]|[1-2][0-9]{2}|3[0-4][0-9]|350

1から1000までの数値
[1-9]|[1-9][0-9]|[1-9][0-9]{2}|1000

数値の場合、型が変わるところで区切って考えるとわかりやすいと思います。
|(バーティカルバー)で区切ることで、「または」の条件として正規表現を記述出来ます。

ABC.EDFのドットから前の文字列
[A-Z]+\.

これでABC.にマッチします。
.や*など一部の特殊文字を正規表現として扱う場合はエスケープする必要があります。


ちなみに、正規表現を試すとき、1回1回書いてビルドして実行・・・とやっていると時間がかかってしまいます。
そんな時は、Web上で正規表現が完全一致、もしくは部分一致するかを試すことが出来るサイトがあります。

Regex Test Drive | 正規表現オンラインテストサイト

ここで試行錯誤してみるのも良いかもしれません。