日時の表記や通貨の単位などロケールに依存した処理はいろいろあります。

たとえば、第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を返します。