Unicodeの互換等価をJavaで扱う方法

こんにちは!

カサレアルでJavaのコースを担当している櫻庭です。

前回のブログエントリーは、Javaで文字列を比較する場合、Unicodeの正規等価を考慮して比較しましょうという話題でした。

Javaの文字列比較はequalsでいいの?
https://bsblog.casareal.co.jp/archives/13148

今回は、前回の続きで、Unicodeのもう1つの等価である互換等価をJavaで扱う方法を紹介します。

互換等価

住所の入力が必要なシステムを実装していることを考えてみましょう。

このような場合、郵便番号や番地などを正確に比較する必要があります。つまり、「1」のような全角の数値と、「1」のような半角の数値が同じ数値を表していることを考慮しなくてはなりません。

かといって「数字は全角で入力してください」などと指定するのは、使いづらいシステムになってしまいます。

住所の表記はとてもあいまいで、自前で住所を正規化するのはとても大変です。このため、住所を正規化するための専用のライブラリもあります。

住所全体の比較とまではいかなくても、郵便番号や番地の数値の比較であっても、「1」と「1」を同じ意味を表すものとして考慮しなくてはなりません。

「1」と「1」のように見た目は異なるのですが、意味的には等価とみなせる文字をUnicodeでは互換等価と定義しています。

Unicodeが定義している互換等価の文字には、幅の違い(たとえば「1」と「1」や、半角カナと全角カナなど)や、丸付きの有無(たとえば「1」と「①」)などがあります。

しかし、「1」と漢数字の「一」は互換等価とは定義されていないので、注意が必要です。

これらの互換等価を考慮して比較するには、互換等価用の正規化を行う必要があります。

互換等価の正規化

互換等価の文字を比較するための正規化には2種類の形式があります。

  • 互換分解 Normalize Form Compatibility Decomposite (NFKD)
  • 互換合成 Normalize Form Compatibility Composite (NFKC)

互換分解は互換等価性によって分解を行います。正規等価のエントリーで説明したように、合成文字は複数文字に分解されます。

互換合成の方は、一度互換分解した後に再び合成する正規化です。

この2種類の正規化をJavaで行うには、正規等価の場合と同じくjava.text.Normalizerクラスのnormalizeメソッドを使用します。

normalizeメソッドの引数は2つで、第1引数は正規化を行う文字列、第2引数が正規化の形式を表す列挙型のNormalizer.Formです。戻り値が正規化した文字列になります。

たとえば、「1-1-2」と「1-①-2」を比較することを考えてみます。NFKDとNDKCのどちらの正規化を使ってもよいのですが、ここではNFKCを使ってみます。

void main() {
  var text1 = "1-1-2";
  var text2 = "1-①-2";

  // text1の正規化 - NFKCを使用
  var normalizedText1 
      = Normalizer.normalize(text1, Normalizer.Form.NFKC);
  IO.println("Text 1: " + normalizedText1);

  // text2の正規化 - NFKCを使用
  var normalizedText2 
      = Normalizer.normalize(text2, Normalizer.Form.NFKC);
  IO.println("Text 2: " + normalizedText2);

  // 正規化後の文字列を比較
  System.out.println(normalizedText1.equals(normalizedText2));
}

実行してみると次のようになりました。

比較の実行結果

これを見ると、「1-①-2」は「1-1-2」に正規化されたことが分かります。数字だけでなく、全角のマイナスも半角に正規化されていることが分かります。

この正規化によって、2つの文字列は等しいという結果になりました。

ちょっと間違えやすいのが、マイナスを表す「-」(U+002D)と「ー」(U+2212)は互換等価ですが、ハイフン「-」(U+0027)やダッシュ「–」(U+2013)とは互換等価ではないということです。見た目は同じに見えるのですが、表している意味が異なるということで互換等価ではないのです。

このように、互換等価として定義されている文字を正しく認識しておかないと、思わぬ間違いにつながる可能性があります。互換等価で比較するのは数値だけに限定しておいたほうがよいでしょう。

まとめ

Unicodeの互換等価について紹介し、Javaで互換等価を扱うための正規化について解説しました。これで、Unicodeの正規等価と互換等価という2つの等価性があることを理解いただけたはずです。

Unicodeには他にも様々な特徴があり、その特徴をプログラムでも扱わなければいけないことも多くあります。他のUnicodeの特徴については、また別の機会に取り上げていきます。

 

カサレアルでは、Javaを学ぶ方に向けて「Javaプログラミング入門」や「Javaプログラミング基礎」などのコースを開催しています。

Javaに関するコースの詳細や開催日程に関しては以下のリンクをご覧ください。

バックエンド開発を学ぶ研修一覧
https://www.casareal.co.jp/ls/service/openseminar/search/backend

--------------------------
開発支援・技術研修のご要望・ご相談はこちらから
--------------------------
【この技術ブログを読んだエンジニアの皆様へ】
カサレアルブログをお読みいただき、ありがとうございます!

私たちは、常に新しい技術に挑戦し、ユーザーのニーズに応えるサービスを提供しています。
もし、当社の技術への情熱や、会社・チーム・社員の雰囲気に共感いただけたなら、
ぜひ私たちと一緒に働きませんか?
現在、株式会社カサレアルでは事業拡大に伴い、新たな仲間となるエンジニアを積極的に募集しています。

少しでも興味をお持ちいただけましたら、まずは弊社のことを知っていただけると嬉しいです。
▼採用サイト
https://www.casareal.co.jp/recruit/career
▼社員インタビュー
https://hrmos.co/pages/casareal/jobs/0000016
▼エンジニアの仲間になる! エントリーはこちらから
https://hrmos.co/pages/casareal/jobs

皆様のエントリーを心よりお待ちしています!

OutSystems開発が快速に!?知らなきゃ損するショートカットキー

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

コメント ※

名前 ※

メール ※

サイト