日時の表記や通貨の単位などロケールに依存した処理はいろいろあります。
たとえば、第51回と第52回で取りあげた和暦もロケールに応じて選択されることを紹介しました。
Javaではすでに様々なロケールが用意されています。頻繁に使用されるen_USやja_JPなどはjava.util.Localeクラスの定数として定義されています。
定数で定義されたロケール以外にも、アラビア語を表すarから、台湾を表すzh_TWまで、なんと152種類ものロケールがサポートされています。
しかし、世の中にはもっと様々なロケールが存在しています。
Unicodeコンソーシアムが策定しているCommon Locale Data Repository (CLDR)では400以上のロケールが定義されています。
これらのロケールには、ISO 639-2において定義されている3文字による言語コードが含まれています。ところが、Localeクラスでは3文字の言語コードを扱えないので、CLDRのすべてを使うことはできません。それらを除いたとしても、300以上のロケールを定義されています。
Javaでサポートされていないロケールを使用する場合、ロケールに依存した処理も更新しなくてはなりません。そのために、使用されるのがJava SE 6で採用されたLocale Sensitive Services SPIです。
Locale Sensitive Services SPIでは以下の9種類のクラスに対して、新たなロケールに対応させることが可能です。
- java.util.Locale
- java.util.TimeZone
- java.util.Currency
- java.text.BreakIterator
- java.text.Collator
- java.text.DateFormat
- java.text.DateFormatSymbols
- java.text.NumberFormat
- java.text.DecimalFormatSymbols
ここでは、NumberFormatクラスを新しいロケールに対応させてみましょう。
NumberFormatクラスでは通貨のフォーマットもできます。日本語環境では10円は「¥ 10」とフォーマットされます。
しかし、できれば「10円」とフォーマットしてみたくないですか。もちろん、NumberFormatクラスの派生クラスであるjava.text.DecimalFormatクラスを使用すれば、「10円」とフォーマットすることも可能です。
とはいうものの、せっかくの機会ですので、ロケールに応じて「¥ 10」と「10円」を切り替えられるようにしてみましょう。
そのためには、「10 円」と書かせるためのロケールと、ロケールに応じた処理を行うサービスプロバイダを作成しなくてはなりません。
当初、Java SE 6で新たにサポートされたja_JP_JPロケールを使用しようとしたのですが、どうやらすでにサポートされているロケールに対して動作を変更することはできないようです。
しかたないので、新しいロケールを作ってしまいました。ロケールは言語コード、国コード、バリアントから成り立っています。言語はja、国はJPという部分はそのままにして、バリアントをYENとしてみました。つまり、ja_JP_YENです。
次に、このロケールの時の、通貨のフォーマットを決めなくてはなりません。
ロケールに応じて処理を変化させるために用意されているのが、java.util.spi.LocaleServiceProviderクラスです。
上述した9種類のクラスはすべてLocaleServiceProviderクラスを派生させたサービスプロバイダが用意されています。いずれのクラスもjava.text.spiパッケージ、もしくはjava.util.spiパッケージに属しています。
通貨のフォーマットを行うのはNumberFormatクラスであり、そのサービスプロバイダに相当するのがjava.text.spi.NumberFormatProviderクラスなのです。
そこで、NumberFormatProviderクラスを派生させてYenNumberFormatProviderクラスを作成しました。
public class YenNumberFormatProvider extends NumberFormatProvider { private final static Locale YEN = new Locale("ja", "JP", "YEN"); public Locale[] getAvailableLocales() { // このクラスがサポートしているロケールの一覧を返す return new Locale[] {YEN}; } // 通貨フォーマット public NumberFormat getCurrencyInstance(Locale locale) { if (YEN.equals(locale)) { // 円で表示するための DecimalFormatを生成 return new DecimalFormat("#,###円"); } else { // ja_JP_YEN以外は対応できないのでnullを返す return null; } }
getAvailableLocalesメソッドはLocaleServiceProviderメソッドで定義されている唯一のメソッドです。
このメソッドではサービスプロバイダでサポートしているロケールの一覧を返します。戻り値の型はLocaleクラスの配列です。
YenNumberFormatProviderクラスではja_JP_YENロケールにだけ対応しているので、青字で示したようにja_JP_YENロケールを配列にして返しています。
これ以外のメソッドはサービスプロバイダごとに定義されています。
NumberFormatProviderクラスでは次の4つのメソッドが定義されています。
- NumberFormat getCurrencyInstance(Locale locale)
- NumberFormat getIntegerInstance(Locale locale)
- NumberFormat getNumberInstance(Locale locale)
- NumberFormat getPercentInstance(Locle locale)
それぞれ、NumberFormatクラスの同名メソッドに対応しています。
たとえば、NumberFormatクラスのgetCurrencyInstanceメソッドがコールされると、引数のロケールがNumberFormatクラスでサポートしていない場合、サービスプロバイダの同名メソッドがコールされるわけです。
さて、このサンプルでは通貨フォーマットだけをおこなうので、getCurrencyInstanceメソッドを使用します。
ロケールがja_JP_YENの場合、赤字で示しているようにDecimalFormatオブジェクトを生成して返しています。フォーマットのパターンは「#,###円」としました。これは3桁ごとにカンマが表示され、数字の後ろに「円」が表示されるパターンです。
このクラスで扱えないロケールの場合、nullを返します。