この記事は、先日の potatotips #22 では説明しきれなかった部分を補足するものです。potatotips #22 でのスライドは Regular Expression in Android And Java です。
おさらい
Java の正規表現
Pattern
クラスを用いて、正規表現文字列をコンパイルしてマッチする処理を描くことになります。String
クラスには一部便利なメソッドがあり、正規表現文字列を渡してマッチするものに処理を施す事ができるようになっていますが、あまり複雑なことはできないため、String
クラスで提供されている以上のことをする場合はPattern
クラスを用います。
Java の正規表現は Perl5 のシンタックスのサブセットですが、Perl のように正規表現リテラルは存在せず、文字列を用いて正規表現を記述するため、特別なエスケープ処理が必要な場合があります。
Pattern.compile
を使って正規表現文字列をコンパイルするとき、int
の flag を付与することで、マッチする条件をコントロールすることができます。
Android の正規表現
すべての正規表現 API は Java のものと同じものが提供されています。Pattern
クラスを用いて正規表現文字列をコンパイルしてマッチするという機能も同じです。
少しずつ違う Java と Android の正規表現の結果
文字クラス
例えば、このブログ記事では、文字クラスを用いた時の結果の違いを説明しています。
フラグ
Java のクラスをそのままポートしていますが、Android ではサポートしないフラグが存在します。
現状では、CANON_EQ
(Canonical Equivalence)フラグはサポートしておらず、これを指定するとUnsupportedOperationException
がスローされます。それ以外に、定義外のフラグをセットした場合はIllegalArgumentException
がスローされます。
なにが違うのか
根本的には、Java と Android で使用している正規表現エンジンが異なります。Java は独自のエンジンを持っていますが、Android は ICU が提供しているエンジンを使用しています。このため、微妙ながら満たしている仕様が違ってきます。
両者とも、Unicode Technical Standard #18 Unicode Regular Expressions Level1 の仕様を満たしますが、この他 Java は 2.1 Canonical Equivalents を満たし、一方 Android は 2.3 Default Word Boundaries と 2.5 Name Properties を満たします。Android でCANON_EQ
フラグが使えないのは Canonical Equivalents の仕様を満たしていないからです。
困ること
Robolectric vs Runtime
Robolectric のような、JVM 上でテストを動かすものの場合、実行時とユニットテスト時で結果が異なります。
IntelliJ の Regexp Check
IntelliJ の機能として Regexp Check があり、正規表現文字列にフォーカスした状態で補完メニューを表示すると正規表現の動作チェックができます。この機能はもちろん JVM 上で解釈されるので、Android のランタイムとは結果が異なる場合があります。
なぜ違うのか
一つにはライセンスの問題がありそうです。また、ICU は独立したプロジェクトとして、Unicode 標準を常にトラッキングしていたり、パフォーマンスのチューニングが施されていたりすることもありそうです。実際どの程度 Java のエンジンとくらべて良いかは測定できていませんが……
さいごに
Android の Javadoc には、Implementation Notes として特別な項目があります。Android の正規表現エンジンは他の Java 言語の実装のスーパーセットですので、一部のレアケースを除いては Java 言語での正規表現がそのまま使用できます。ただし、一部 Java 言語の実装では動作しない正規表現が、Android では動作する場合があります。