Android Javaの正規表現の落とし穴

Androidで正規表現を使っていて、全角のパターンマッチで少しはまったのでメモ。

Javaでは他の正規表現エンジンと同様、POSIX文字クラスが利用できます。

if("Hello".match("^\\p{Alpha}+$")){
System.out.println("アルファベットだけ!");
}

標準Java

Java の正規表現では US ASCII Only とかかれているように、ひらがな等ASCII以外の文字にはマッチしません。

それぞれの文字について、POSIX文字クラス(“\\p{クラス名}”)がマッチするかを調べた結果、このようになりました。

(左が判定対象の文字、右が該当するクラス)
a: Graph,Alnum,Print,Lower,Alpha,ASCII,
b: Graph,Alnum,Print,Lower,Alpha,ASCII,
c: Graph,Alnum,Print,Lower,Alpha,ASCII,
d: Graph,Alnum,Print,Lower,Alpha,ASCII,
e: Graph,Alnum,Print,Lower,Alpha,ASCII,
z: Graph,Alnum,Print,Lower,Alpha,ASCII,
A: Graph,Alnum,Print,Upper,Alpha,ASCII,
B: Graph,Alnum,Print,Upper,Alpha,ASCII,
C: Graph,Alnum,Print,Upper,Alpha,ASCII,
D: Graph,Alnum,Print,Upper,Alpha,ASCII,
E: Graph,Alnum,Print,Upper,Alpha,ASCII,
Z: Graph,Alnum,Print,Upper,Alpha,ASCII,
0: Graph,Alnum,Print,Digit,ASCII,
1: Graph,Alnum,Print,Digit,ASCII,
2: Graph,Alnum,Print,Digit,ASCII,
3: Graph,Alnum,Print,Digit,ASCII,
4: Graph,Alnum,Print,Digit,ASCII,
9: Graph,Alnum,Print,Digit,ASCII,
: Print,ASCII,,(半角スペース文字)
: ASCII,(TAB文字)
!: Graph,Print,Punct,ASCII,
": Graph,Print,Punct,ASCII,
#: Graph,Print,Punct,ASCII,
$: Graph,Print,Punct,ASCII,
%: Graph,Print,Punct,ASCII,
&: Graph,Print,Punct,ASCII,
\: Graph,Print,Punct,ASCII,
_: Graph,Print,Punct,ASCII,
-: Graph,Print,Punct,ASCII,
あ:
い:
う:
え:
お:
ア:
イ:
ウ:
エ:
オ:
ア:
イ:
ウ:
エ:
オ:
A:
B:
C:
D:
E:
$:
: (全角スペース)

このように、全角文字、日本語等は全てマッチしません。

Android Java

Android の Java でも同様にPOSIX拡張文字クラスを利用できるのですが、標準Javaとは挙動が変わるようで、ひらがなにもマッチしてしまいます。

(左が判定対象の文字、右が該当するクラス)
a: Graph,Alnum,Print,Lower,Alpha,ASCII,
b: Graph,Alnum,Print,Lower,Alpha,ASCII,
c: Graph,Alnum,Print,Lower,Alpha,ASCII,
d: Graph,Alnum,Print,Lower,Alpha,ASCII,
e: Graph,Alnum,Print,Lower,Alpha,ASCII,
z: Graph,Alnum,Print,Lower,Alpha,ASCII,
A: Graph,Alnum,Print,Upper,Alpha,ASCII,
B: Graph,Alnum,Print,Upper,Alpha,ASCII,
C: Graph,Alnum,Print,Upper,Alpha,ASCII,
D: Graph,Alnum,Print,Upper,Alpha,ASCII,
E: Graph,Alnum,Print,Upper,Alpha,ASCII,
Z: Graph,Alnum,Print,Upper,Alpha,ASCII,
0: Graph,Alnum,Print,Digit,ASCII,
1: Graph,Alnum,Print,Digit,ASCII,
2: Graph,Alnum,Print,Digit,ASCII,
3: Graph,Alnum,Print,Digit,ASCII,
4: Graph,Alnum,Print,Digit,ASCII,
9: Graph,Alnum,Print,Digit,ASCII,
: Print,ASCII,(半角スペース文字)
: ASCII,(TAB文字)
!: Graph,Print,Punct,ASCII,
": Graph,Print,Punct,ASCII,
#: Graph,Print,Punct,ASCII,
$: Graph,Print,ASCII,
%: Graph,Print,Punct,ASCII,
&: Graph,Print,Punct,ASCII,
\: Graph,Print,Punct,ASCII,
_: Graph,Print,Punct,ASCII,
-: Graph,Print,Punct,ASCII,
あ: Graph,Alnum,Print,Alpha,
い: Graph,Alnum,Print,Alpha,
う: Graph,Alnum,Print,Alpha,
え: Graph,Alnum,Print,Alpha,
お: Graph,Alnum,Print,Alpha,
ア: Graph,Alnum,Print,Alpha,
イ: Graph,Alnum,Print,Alpha,
ウ: Graph,Alnum,Print,Alpha,
エ: Graph,Alnum,Print,Alpha,
オ: Graph,Alnum,Print,Alpha,
ア: Graph,Alnum,Print,Alpha,
イ: Graph,Alnum,Print,Alpha,
ウ: Graph,Alnum,Print,Alpha,
エ: Graph,Alnum,Print,Alpha,
オ: Graph,Alnum,Print,Alpha,
A: Graph,Alnum,Print,Upper,Alpha,
B: Graph,Alnum,Print,Upper,Alpha,
C: Graph,Alnum,Print,Upper,Alpha,
D: Graph,Alnum,Print,Upper,Alpha,
E: Graph,Alnum,Print,Upper,Alpha,
$: Graph,Print,
: Print,(全角スペース)

このように、Android の Javaでは英字は全半角問わず判定するようになり、日本語文字もアルファベットクラス(Alpha)として識別されるようになっています。どうもUnicodeの文字判定に依存しているようです。

これは定義済み文字クラス全てに言えるようで、たとえば空白を表す文字クラス \s は、Androidでは全角スペースにもマッチしました。

if("これは全角スペース のテストです".match("\\s")){
System.out.println("空白文字が含まれます"); // Androidではこれを表示。
}else{
System.out.println("空白文字が含まれません"); // Java SEではこれを表示。
}

Android の Javaの正規表現で全半角を区別したい場合は、
[a-z] (半角)や[A-Z](全角)のように文字範囲を明示すればAndroidでも意図した動作になります。

private void printSampleRegex(String s){
	if(s.match("[ \t]")){
		System.out.println("半角空白文字");
	}
	if(s.match(" ")){
		System.out.println("全角スペース");
	}
	if(s.match("[a-zA-Z0-9]")){
		System.out.println("半角英数字");
	}
	if(s.match("[a-zA-Z0-9]")){
		System.out.println("全角英数字");
	}
	if(s.match("[ぁ-ん]")){
		System.out.println("ひらがな");
	}
	if(s.match("[ァ-ヶ]")){
		System.out.println("カタカナ");
	}
}

POSIX 正規表現の仕様では文字クラスはロケールの違いを吸収するために提供されるものと定義しているので標準Javaより仕様に忠実と言えますが、標準Javaのつもりでうっかり間違わないように注意が必要ですね。