国際化プログラミングで避けて通れないのが、ユーザインターフェースなどで使用される文字列です。

たとえば、メニューバーの文字列は英語であれば「File」と表記し、日本語であれば「ファイル」と表記します。言語に応じて変化させるということは、ロケールに応じて変化させるということと同じです。

つまり、これらの文字列はそれぞれのロケールごとに用意しておき、実行時に差し替えます。このようにロケールごとに変化させる文字列などをまとめて地域化リソースと呼びます。

そして、地域化リソースを扱うためのクラスがjava.util.ResourceBundleクラスです。

Java SE 6では、このResourceBundleクラスの機能が大幅に強化されました。

そこで、今週はResourceBundleクラスの基本的な使用方法を紹介し、来週から新しい機能について紹介していくことにしましょう。

ResourceBundleクラスの使い方

ResourceBundleクラスの使用方法はとても簡単です。しかし、その動作原理を理解しておかないと、望んだリソースが取得できないこともあります。

サンプルのソース ResourceBundleSample1.java

このサンプルでは、System.out.printlnメソッドで出力する文字列をResourceBundleオブジェクトから取得します。

    public ResourceBundleSample1() {
        ResourceBundle resources 
            = ResourceBundle.getBundle("net.javainthebox.resources");
         
        System.out.println(resources.getString("hello.world"));
    }

まず、ResourceBundleオブジェクトを取得します。これには、赤字で示したように、ResourceBundleクラスのstaticメソッドであるgetBundleメソッドを使用します。

getBundleメソッドの引数がリソースバンドルのファミリ名となります。パッケージを使用する場合は、パッケージ名を記述した後、続けてファミリ名を記述します。

上記のコードではnet.javaintheboxがパッケージで、resourcesがファミリ名になります。

また、getBundleメソッドにはロケールを引数に取るものがオーバロードされています。ロケールを指定しない場合、デフォルトロケールが使用されます。

リソースは文字列のキーとペアで保持されており、リソースを取得するには、getObjectメソッド、getStringメソッド、getStringArrayメソッドのいずれかを使用します。

リソースが文字列以外の場合に使用するのがgetObjectメソッド、文字列の場合はgetStringメソッドになります。文字列の配列の場合、getStringArrayメソッドを使用します。

このいずれのメソッドも引数はキーとなる文字列です。

上記のコードでは文字列のリソースを扱っているので、getStringメソッドを使用しています。また、引数のキーはhello.worldです。

リソースの記述

リソースを記述するためには、次の2種類の方法があります。

  • クラスとして記述する
  • プロパティファイルに記述する

クラスとして記述する方法は、java.util.ListResourceBundleクラスの派生クラスとしてリソースを記述します。

この時、リソースバンドルのファミリ名がクラス名となります。したがって、上記のコードでは、パッケージがnet.javainthebox、クラス名がresourcesとなります。

package net.javainthebox;
 
import java.util.ListResourceBundle;
 
public class resources extends ListResourceBundle {
    private final static String resources[][] = {
        {"hello.world", "Hello, World! - resources class"}
    };
    
    protected Object[][] getContents() {
        return resources;
    }
}

ListResourceBundleクラスの派生クラスでオーバライドしなくてはいけないのが、赤字で示したgetContentsメソッドです。

getContentsメソッドの戻り値はObjectクラスの二重配列です。キーとリソースのペアを配列にし、それを複数保持しています。

できあがったresourcesクラスが、デフォルトのリソースバンドルとなります。

さて、ここに日本語のリソースバンドルも加えましょう。日本語を加えるにはファミリ名に_jaを付け加えたクラスを作成します。

public class resources_ja extends ListResourceBundle {
    private final static String resources[][] = {
        {"hello.world", "こんにちは、世界! - resources_ja クラス"}
    };
    
    protected Object[][] getContents() {
        return resources;
    }
}

同じようにロケールja_JPに対応したリソースはファミリ名に_ja_JPを付け加えたクラスになります。

public class resources_ja_JP extends ListResourceBundle {
    private final static String resources[][] = {
        {"hello.world", "こんにちは、世界! - resources_ja_JP クラス"}
    };
    
    protected Object[][] getContents() {
        return resources;
    }
}

ロケールのバリアントに対応させたければ、バリアントまで含めたロケールを付加したクラスを作成します。

もちろん、クラスでリソースを記述したら、javacでコンパイルしておかないとダメです。

次にプロパティファイルで記述する方法です。

ファイルで記述する場合、ファミリ名に拡張子propertiesを加えたファイル名にします。この場合はresources.propertiesがファイル名となります。また、パッケージと同一のディレクトリに配置する必要があります。

hello.world=Hello, World! - resources.properties.

プロパティファイルはキーが左辺、リソースが右辺となるように記述します。

このファイルがデフォルトリソースバンドルに相当します。

クラスでリソースを記述したのと同様に、日本語ロケールの場合ファイル名はresources_ja.properties、ロケールja_JPの場合ファイル名はresources_ja_JP.preopertiesとなります。

プロパティファイルに日本語などを使う場合、Unicodeエスケープで記述する必要があります。たとえば、resources_ja.propertiesの内容を次のようにしたとしましょう。

hello.world=こんにちは、世界! - resources_ja.properties

これをnative2asciiツールを使用してUnicodeエスケープに変換します。変換すると次のようになります。

hello.world=\u3053\u3093\u306b\u3061\u306f\u3001\u4e16\u754c!
 - resources_ja.properties

プロパティファイルで記述されたリソースを扱うのはjava.util.PropertyResourceBundleクラスですが、プログラムの中で直接使用することはありません。

さて、リソースを記述できたので、サンプルを実行してみましょう。

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

デフォルトロケールであるja_JPに対応したリソースが選択されたことが分かります。

リソースバンドルの検索

リソースの記述にはクラスを使用する方法とプロパティファイルを用いる方法を紹介しました。この両者を混在させて使用することもできます。

重要なのは、複数のロケールに対応したリソースを記述した場合、クラスもしくはプロパティファイルの選択順序を理解しておくということです。

ResourceBundleクラスは条件に合致するリソースバンドルを検索し、はじめに見つかったリソースバンドルを使用します。

ここで、指定されたロケールがlan_NA_VAであり、デフォルトロケールがdeflan_DEFNA_DEFVA、またリソースバンドルのファミリ名がresourceであったとしましょう。この場合、ロケールの検索順序は次のようになります。

  1. 指定されたロケールに基づいて次の順序で検索を行います。
    バリアントや国コードが指定されなかった場合、それに対応するリソースバンドルは検索されません。
    1. resource_lan_NA_VA.class
    2. resource_lan_NA_VA.properties
    3. resource_lan_NA.class
    4. resource_lan_NA.properties
    5. resource_lan.class
    6. resource_lan.properties
    まず、指定されたロケールlan_NA_VAに対応するクラスを検索し、次にプロパティファイルを検索します。これで見つからなければ、バリアントを省略したロケールに対応したクラス、そしてプロパティファイルを検索します。これでも見つからない場合は、言語コードだけでクラス、そしてプロパティファイルを検索します。
  2. 発見できない場合はデフォルトロケールに基づいて次の順序で検索を行います。
    1. resource_deflan_DEFNA_DEFVA.class
    2. resource_deflan_DEFNA_DEFVA.properties
    3. resource_deflan_DEFNA.class
    4. resource_deflan_DEFNA.properties
    5. resource_deflan.class
    6. resource_deflan.properties
    ロケールを指定した場合と同様に、バリアントも含めたクラス/プロパティファイルから検索し、発見できなければバリアントを省略したクラス/プロパティファイルを検索します。発見できなかった場合も同様に、言語コードだけで検索を行います。
  3. デフォルトのリソースバンドルを次の順序で検索します。
    1. resource.class
    2. resource.properties

もし、リソースバンドルが発見できなかった場合、java.util.MissingResourceException例外が発生します。

前節の実行結果はデフォルトロケールで検索を開始し、デフォルトロケールに対応するクラスファイルresources_ja_JP.classが発見できたため、それを利用しています。

この結果はクラスファイルの方がプロパティファイルよりも優先して検索されるため、resources_ja_JP.propertiesが存在していたとしても変化しません。

ここまで、ResourceBundleクラスの使い方を紹介してきましたが、面倒くさいと思えるところがいくつかありませんか?

たとえば、日本語のプロパティファイルはnative2asciiツールでUnicodeエスケープ表示に変更しなくてはいけないことや、プロパティファイルしか作成していないのにわざわざクラスファイルを検索しにいくところなどです。

と、このように書いたら、Java SE 6ではそれが解決していることが分かってしまいますね。その新機能の紹介は来週からのお楽しみということにしておきましょう。

 

著者紹介 櫻庭祐一

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

今月の櫻庭

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

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

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

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