Java 6が対応しているUnicodeのバージョンは4.1だそうです 【追記: 後で確かめたら4.0だそうです。私の思い違いでした _o_】。IVSに使われる面0Eのvariation selector (異体字セレクタ)はこのバージョンには既に入っています。なので、最新IVDの知識は期待できないとしても、variation selectorを無視するくらいの処理はひょっとしたら入っていてくれないかな、と思ってちょっと試してみました。
まず、Stringクラスの挙動から。まあ、何が起こるかは大体想像できるのですが、一応確認しておきましょう。
こんな風な文字列があるとします。
String nonIvs = "与太郎"; String ivs1 = "与\uDB40\uDD00太郎"; String ivs2 = "与\uDB40\uDD02太郎";
ここで、\uDB40\uDD00
というのはU+E0100の、\uDB40\uDD02
というのはU+E0102のエスケープ表現です。つまり、この3つの文字列はいずれも「与太郎」という文字列を表しています。ivs1とivs2の「与」にはそれぞれU+E0100とU+E0102という異体字セレクタがついていますが、現在のIVDによるとこれはいずれも常用漢字体の (つまり、普通の)「与」の形であり、見た目に区別がつきません。つまり、この三つの文字列はIVS対応の環境で表示するといずれも「与太郎」と見えます。
この文字列をまずはString#equals()で比較してみましょう。
System.out.println("\"" + nonIvs + "\".equals(\"" + ivs1 + "\") = " + nonIvs.equals(ivs1)); System.out.println("\"" + ivs1 + "\".equals(\"" + ivs2 + "\") = " + ivs1.equals(ivs2));
結果はこうなります。
"与太郎".equals("与[0E0100]太郎") = false "与[0E0100]太郎".equals("与[0E0102]太郎") = false
ただし、[0E0100] は、U+E0100のバイト列を表します。私の実験環境では四角の中に 0E0100 という文字が入った格好で表示されています。
これは想像通りですね。単にcharの列を比較しただけの結果になっています。つまり、見た目の区別がつかなくても容赦なく別々の文字列として扱っています。これが何をもたらすかというと、Stringクラスで比較しているプログラムに対しては、"与" と "与[0E0100]" と "与[0E0102]" を、見た目の区別がないにもかかわらず、人が入力し分けてやらないと困ることになるということです。
String#compareTo() も試しておきましょう。比較のためにもう一つ文字列を定義しておきます。
String nonIvsNext = "与那国";
「那」はUnicode順でもJISコード順でも「太」より後になります。つまり、「与太郎」と「与那国」を比較すると「与那国」の方が後にくることが期待されます。実際、IVSを含まない文字列同士での比較、
System.out.println("\"" + nonIvs + "\".compareTo(\"" + nonIvsNext + "\") = " + nonIvs.compareTo(nonIvsNext));
を実行すると、負の値が出力されます。これは期待通りです。
IVSが絡むとどうなるか。
System.out.println("\"" + nonIvsNext + "\".compareTo(\"" + ivs1 + "\") = " + nonIvsNext.compareTo(ivs1));
を実行すると、
"与那国".compareTo("与[0E0100]太郎") = -1910
と、負の値になります。「与那国」の方が「与[0E0100]太郎」より先だといっているのです。これは、見た目の期待に反します。残念。(まあ、Stringクラスにそこまで期待しない方がいいのですが)
気をとりなおして、Collatorでうまいこと処理してくれないものか、実験してみます。
JavaにはCollatorというものがあります。存在理由や効果などは『プログラマのための文字コード技術入門』の第7章を参照してほしいのですが、これを使うと文字コード順でなく言語に応じた適切な照合(collation)を実現できます。
準備として、デフォルトロケール (この場合は日本語環境) のCollatorオブジェクトを取得しておきます。
Collator c = Collator.getInstance();
このCollatorで比較するとどうなるか。人が同じと認識する文字列は同じだという結果を返してほしいわけです。
System.out.println("Compare " + ivs1 + " with " + nonIvs + ": " + c.compare(ivs1, nonIvs));
を実行すると、
Compare 与[0E0100]太郎 with 与太郎: 1
となります。等しい文字列のときには0という値が返却されるのですが、ここで1と出ているのは、「与[0E0100]太郎」の方が大きい文字列だといっているわけです。念を押すようですが、見た目の区別はつきません。
IVS同士だとどうか。
System.out.println("Compare " + ivs1 + " with " + ivs2 + ": " + c.compare(ivs1, ivs2));
この結果はこうです。
Compare 与[0E0100]太郎 with 与[0E0102]太郎: -1
「与[0E0100]太郎」の方が小さいといっています。同じとはみなしてくれません。この二つは、見た目の区別のつかないIVS同士です。
どうも、Collatorを使っても、異体字セレクタを無視して比較するような賢いことはしてくれず、単に漢字の後ろに面0Eの文字があるような処理がされています。
だめおしにもうひとつ。
System.out.println("Compare " + nonIvs + " with " + nonIvsNext + ": " + c.compare(nonIvs, nonIvsNext)); System.out.println("Compare " + nonIvsNext + " with " + ivs1 + ": " + c.compare(nonIvsNext, ivs1));
この2行の実行結果は下のとおり。
Compare 与太郎 with 与那国: -1 Compare 与那国 with 与[0E0100]太郎: -1
IVSなしの「与太郎」と「与那国」では常識的に負の値が返却されています。一方、「与那国」と「与[0E0100]太郎」とでは、前者の方が小さいといっています。見た目の文字列としては「与那国」と「与太郎」の比較なので正の値がかえってほしいところですが、そうはなってくれません。もし「与太郎」(IVSなし)「与那国」「与[0E0100]太郎」の三つの文字列をソートすると、Collatorを使ったとしても、異体字セレクタの入るものが一番後にくるという格好悪い結果になってしまいます。見た目のソート結果としては「与太郎」「与那国」「与太郎」という風になり、なぜ「与那国」の後に「与太郎」がひとつだけ来ているのか、人が見てもさっぱり分からないという状況になります。
結論としては、予想されたことではありますが、Java 6はIVSについて特別なことは何もしてくれません。なので、IVSを含むデータを考えなしに垂れ流すと、いろいろ困ったことになるでしょう。UTS #37のIVDの仕様には「variation selectors are default ignorable」と書いてありますが、Javaはignoreしてくれないようです。
文字列がIVSを含む場合の照合(collation)についてUnicodeの人が何も考えていないとは思えないのですが、UTS #10 「Unicode Collation Algorithm」には異体字セレクタへの言及が何もありません。このあたりがどうなっているのか、もしご存じの方がいたら教えていただけるとうれしいです。(まあ、variation selectorは単に無視せよということなのかもしれませんが、どこかでそう明言されていないと実装者は何も考えなさそうな気がするので)
コメントする