先週はjava.util.ResourceBundleクラスの基本的な使い方を解説しました。

今週はResourceBundleクラスの新しい機能について紹介していきます。

といっても、ResourceBundleクラス本体はそれほど変更がありません。ではどこが変更されたかというと、ResourceBundleクラスの内部クラスであるResourceBundle.Controlクラスが新設されたことなのです。

ResourceBundle.Controlクラスは、ResourceBundleクラスがgetBundleメソッドでリソースバンドルを読み込む際にコールされるコールバックメソッドを定義しています。

ユーザは必要に応じてResourceBundle.Controlクラスを派生させ、getBundleメソッドの引数として使用します。

ResourceBundle.Controlクラスで実現できることは複数あるので、順々に説明していきます。

リソース検索の対象指定

先週、紹介したようにリソースはクラスファイルとして記述する方法と、プロパティファイルとして記述する方法があります。

getBundleメソッドでのリソースバンドルの検索は、クラスファイルとプロパティファイルの両方を検索します。

しかし、クラスファイルだけでリソースバンドルを記述している場合、プロパティファイルを検索することは処理の無駄になります。そこで、リソースバンドルの検索にプロパティファイルを含めないようしてみましょう。

サンプルのソース ResourceBundleSample2.java

このような用途の場合、ResourceBundle.Controlクラスを派生させる必要はありません。ResourceBundle.Controlオブジェクトを返すstaticなgetControlメソッドを私用します。

    public ResourceBundleSample2() {
        // クラスだけを検索させるResourceBuncle.Controlを取得
        ResourceBundle.Control control = 
            ResourceBundle.Control.getControl(
                    ResourceBundle.Control.FORMAT_CLASS);
            
        ResourceBundle resources 
            = ResourceBundle.getBundle("net.javainthebox.resources",
                                       control);
        
        System.out.println(resources.getString("hello.world"));
    }

getControlメソッドの引数であるFORMAT_CLASS定数はクラスのみを検索させる定数です。getControlメソッドで使用できる定数は以下の3種類があります。

  • FORMAT_CLASS
  • FORMAT_PROPERTIES
  • FORMAT_DEFAULT

FORMAT_PROPERTIESはプロパティファイルのみ検索します。FORMAT_DEFAULTはデフォルトで決められている検索を行います。

取得したResourceBundle.ControlオブジェクトはResourceBundleクラスのgetBundleメソッドの引数に指定します。

実行する前に、リソースバンドルとして、./net/javaintheboxディレクトリに.resources_ja_JP.propertiesファイルとresources_ja.classファイルの2種類があったとしましょう。

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

C:\temp>java ResourceBundleSample2
こんにちは、世界! - resources_ja クラス

通常の検索順序であれば、resources_ja_JP.classファイルを検索し、存在しない場合resources_ja_JP.propertiesファイルを検索します。ところが、FORMAT_CLASSが指定されているため、resources_ja_JP.propertiesファイルは検索を行なわず、resources_ja.classがリソースバンドルとして使用されたのです。

ここで、FORMAT_PROPERTIESを指定してみたら、どうなるでしょう。

C:\temp>java ResourceBundleSample2
こんにちは、世界! - resources_ja_JP.properties

予想通り、resources_ja_JP.propertiesファイルが選択されました。

リソース検索のfallback制御

次にご紹介するのは、リソース検索におけるfallback制御です。

fallbackというのは予備のという意味です。リソースバンドルを検索する場合、指定されたロケールに対応したりソースバンドルが存在しない場合、そのfallbackとしてデフォルトロケールに対応したリソースバンドルを検索します。

このfallbackを指定するのが、fallback制御です。

サンプルのソース ResourceBundleSample3.java

fallback制御を行なうことで、デフォルトロケールに対応したリソースバンドルを検索しないようになります。

なぜこんな制御が必要なのでしょう。

たとえば、デフォルトのリソースバンドルを英語で表記しているため、ロケールen_USに対応するリソースバンドルは作成していなかった場合を考えて見ましょう。

この時、英語で表記したいためロケールen_USを使用したら、どのように表示されるでしょう。

    public ResourceBundleSample3() {
        ResourceBundle resources 
            = ResourceBundle.getBundle("net.javainthebox.resources",
                                       new java.util.Locale("en", "US"));
        
        System.out.println(resources.getString("hello.world"));
    }

実行すると、次のようになりました。

C:\temp>java ResourceBundleSample3
こんにちは、世界! - resources_ja クラス

英語で表示したいのに、日本語で表示されてしまいました。これは、指定されたロケールに対応するリソースバンドルがないため、fallbackでデフォルトロケール、つまりロケールja_JPに対応したリソースバンドルが選択されてしまったわけです。

このようなことを防ぐために、fallback制御を行うのです。

ResourceBundleSample3クラスにfallback制御を加えたものを次に示します。

    public ResourceBundleSample3() {
        ResourceBundle.Control control = 
            ResourceBundle.Control.getNoFallbackControl(
                    ResourceBundle.Control.FORMAT_DEFAULT);
            
        ResourceBundle resources 
            = ResourceBundle.getBundle("net.javainthebox.resources",
                                       new java.util.Locale("en", "US"),
                                       control);
        
        System.out.println(resources.getString("hello.world"));
    }

fallback制御を行なう場合も、ResourceBundle.Controlクラスの派生クラスを作成する必要はありません。赤字で示したように、getNoFallbackControlメソッドの戻り値のResourceBundle.Controlオブジェクトを使用します。

getControlメソッドの場合と同じく、引数にはFORMAT_CLASS、FORMAT_PROPERTIES、FORMAT_DEFAULTが使用できます。

これで実行してみましょう。うまくいけば、英語で表示されるはずです。

C:\temp>java ResourceBundleSample3
Hello, World! - resources class

意図したとおりデフォルトのリソースバンドルが選択され、英語で表示が行われました。

ここではFORMAT_DEFAULTを使用しているので、デフォルトのリソースバンドルに対応するクラスファイルを検索し、その後プロパティファイルを検索します。

検索順序の制御

リソースバンドルの検索順序は先週紹介したように、指定されたロケール→デフォルトロケール→ルートロケールの順になります。

この順序を任意のものに変更してみます。このサンプルは、今までのサンプルと違い、ResrouceBundle.Controlクラスを派生させる必要があります。

サンプルのソース ResourceBundleSample4.java

まったく意味がないのですが、ロケールがja_JPであれば、英語ロケールを先に検索するようにしました。

    ResourceBundle.Control control = 
        new ResourceBundle.Control() {
            public List<Locale> getCandidateLocales(
                    String baseName, Locale locale) {
                if (locale.equals(Locale.JAPAN)) {
                    return Arrays.asList(Locale.ENGLISH,
                                         locale,
                                         Locale.JAPANESE,
                                         Locale.ROOT);
                } else {
                    return super.getCandidateLocales(
                        baseName, locale);
                }
            }
        };

ロケールの検索順序を指定するためには、getCandidateLocalesメソッドをオーバーライドします。

getCandidateLocalesメソッドの第1引数はリソースバンドルのファミリ名をパッケージを含めて表記した文字列です。そして、第2引数はResourceBundleクラスのgetBundleメソッドで指定されたロケールになります。getBundleメソッドでロケールを指定しない場合、デフォルトロケールになります。

一方、getCandidateLocalesメソッドの戻り値は検索するロケールの順番を保持するList<Locale>オブジェクトです。

ここでは第2引数のlocaleがロケールja_JPだった場合、次の順序でロケールに対応するリソースバンドルを検索するようにしていできます。

  1. en
  2. ja_JP
  3. ja
  4. ルートロケール

localeがロケールja_JP以外の場合は、親クラスのgetCandidateLocalesメソッドを呼び出します。

今週はリソースバンドルの検索に関して説明しました。来週はリソースバンドルのキャッシュの制御やプロパティファイルのXML化について紹介する予定です。

 

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

早いもので、Java SE 6完全攻略も50回を過ぎてしまいました。昨年の10月から続けているので、すでに1年以上Java SE 6の紹介をしているわけです。

ところが、まだまだ紹介していない機能がいっぱいあります。いったいいつになったら完全攻略が終わるんでしょうね? 筆者にも分かりません(笑)。

終わったときには、すでにJava SE 7がリリースしていたなんてことだけは避けようと思っていますが、さてどうなることやら。

なお、今月の記事はサン・マイクロシステムズ奥津正義氏および神谷結花氏に多大なるご協力をいただきました。この場を借りてお礼を述べさせていただきます。