Javaでの文字数カウント(サロゲートペア)に関する実験

プログラマのための文字コード技術入門」を読んで。
Stringの文字数をカウントする時、String#length()メソッドでは厳密に文字数をカウントできない場合があるという。

実験

実際にそのケースを試してみる。

本来5とカウントしたいところが、7とカウントされてしまった。これは、文字列の中にサロゲートペアに該当する文字が含まれているためである(1文字目と2文字目)。最初の2文字は「齟齬」(そご)ではなく、「齟齬」の異字体である。サロゲートペアの場合、1つの文字に対し1つのchar値が対応するわけではなく、2つのchar値が対応する形になる。String#length()はcharの数をカウントするため、この場合結果は7となってしまう。
そこでJDK1.5から追加されたString#codePointCount()メソッドを利用してカウントしてみる。これは、文字の符号位置の数をカウントするメソッドである。

問題なく5文字と表示された。
ただ、これではまだ正確に文字数を数えられないケースがある。文字列の中に結合文字が含まれていた場合である。例えば「か」と合成用濁点「゛」が組み合わさって1文字となるパターンの場合、「゛」の数もカウントされてしまうこととなる。

そこで、今度は文字列の区切りを検出するjava.text.BreakIteratorクラスを利用する。このクラスは、人が認識するテキストの境界位置を見つける機能を持つ。

合成文字の場合でも5文字とカウントされた。
また、文字列自体の構造を変えてしまうという手もある。

これは、JDK6から導入されたjava.text.Normalizerというクラスを利用して正規合成を行い、結合文字を1つの文字に変換している(「か」+「゛」を 「が」に変換)。
文字列の正規化に関してはこちらを参考 「Java SE 6完全攻略」第56回 文字列の正規化

おわりに

今まで文字数をカウントするのはString#length()メソッドでOKだと思っていた。実際にこれを使って文字数チェックしているプログラムを何度か見たことがある。ただ、サロゲートペアで問題が起きたというのは聞いたことがない。サロゲートペアになるのは非常に特殊でほとんど使われないような漢字であり、Vistaから導入された文字であるため、システムで入力されることはほとんど無いのではと思う。
もしシステムの入力文字数チェック処理でString#length()メソッドが使われていたとしても、可能な文字数が少なくなるわけで、オーバーしてしまうわけではない。そのためさほど問題にならないのではないかと思う。
ただ、サロゲートペアとなる文字が300文字近くあるというのも気持ち悪い。String#charAt()メソッドの場合も、サロゲートペアの場合、同様のことが起きる。システムとしてはサロゲートペアを考慮し厳密に対処した方が望ましいのではと思う。