久しぶりにブログを書いてみたいと思います.
国際化のためのリソースを取り扱うということで, ResourceBundle
クラスについて少し勉強しています. いままで, せいぜい Properties
クラスにロケールが追加された程度のものかなと勝手に思い込んでいましたが, それだけでもないことをいまさらながら知りました.
順番に検索してくれる
たとえば, ./tt4cs/resource/ProgressiveRock.properties
に以下のように記述し,
Asia = Geoff Downes, Steve Howe, Carl Palmer, John Wetton
./tt4cs/resource/ProgressiveRock_en.properties
に以下のように記述し,
ELP = Keith Emerson, Greg Lake, Carl Palmer Yes = Jon Anderson, Tony Kaye, Trevor Rabin, Chris Squire, Alan White
./tt4cs/resource/ProgressiveRock_en_UK.properties
に以下のように記述し,
Yes = Jon Anderson, Bill Bruford, Steve Howe, Chris Squire, Rick Wakeman
そのうえで, 次のようなコードを書いてみたとします.
ResourceBundle progressiveRock = ResourceBundle.getBundle( "tt4cs.resource.ProgressiveRock", new Locale("en", "UK")); String yes = progressiveRock.getString("Yes"); String elp = progressiveRock.getString("ELP"); String asia = progressiveRock.getString("Asia"); System.out.println("[Yes] " + yes); System.out.println("[ELP] " + elp); System.out.println("[Asia] " + asia);
これを実行しますと, 次のような結果を得ることができます.
[Yes] Jon Anderson, Bill Bruford, Steve Howe, Chris Squire, Rick Wakeman [ELP] Keith Emerson, Greg Lake, Carl Palmer [Asia] Geoff Downes, Steve Howe, Carl Palmer, John Wetton
まず, getBundle
メソッドで得た ResourceBundle
インスタンスは, 単に en_UK
ロケールのリソースをバンドルするのみでなく, en
ロケールのリソースや, ロケール指定のないリソースもバンドルしてくれるのだということがわかります. そうであってほしいとは思っていましたが, getBundle
メソッドの第 2 引数に en_UK
ロケールを指定した場合, ひょっとしたら en_UK
ロケールのリソースしか検索してくれなかったりしないだろうかと不安にも思っていたので, 安心しました.
また, キー "Yes"
に該当する値として, en
ロケールのリソースから得られるもの (いわゆる 「90125 イエス」 のメンバーリスト) ではなく, en_UK
ロケールのリソースから得られるもの (全盛期 「こわれものイエス」 のメンバーリスト) が採用されていることから, 検索の順番や優先順位がわかります. すなわち, en_UK
ロケールのリソースを検索し, 見つかればそこで終わりますし, 見つからなければ次は en
ロケールを検索する, ということです. 理にかなっているというか当然そうであってほしいとは思っていましたが, そのとおりで安心しました.
プロパティファイルだけでなくクラスもある
ResourceBundle
クラスがバンドルしてくれるリソースは, プロパティファイルだけではありません. ということを, はじめて知りました. どういうことかというと, 先ほどの例の続きで, たとえば tt4cs.resource
パッケージに ProgressiveRock_en
という名前のクラスがあって, かつそれが ListResourceBundle
クラスを継承している場合, en
ロケールのリソースとしては ProgressiveRock_en.properties
ファイルではなく ProgressiveRock_en
クラスのほうが優先されるということです.
ここで, そんなクラスを, たとえば次のように書いてみます.
package tt4cs.resource; import java.util.ListResourceBundle; public class ProgressiveRock_en extends ListResourceBundle { @Override protected Object[][] getContents() { return new Object[][] { new String[] { "King Crimson", "Robert Fripp, Adrian Belew, Tony Levin, Bill Bruford" } }; } }
ListResourceBundle
は抽象クラスでありまして, 継承する側としては getContents()
メソッドの実装が必要です. Object の 2 次元配列を返す必要がある点が, なんというか, ちょっと見慣れないものを見てしまった気分ですが, まあいいでしょう. そのうえで, 以下のようなコードを書いて, 実行してみます.
String kc = progressiveRock.getString("King Crimson"); System.out.println("[King Crimson] " + kc);
その結果は, 次のとおりです.
[King Crimson] Robert Fripp, Adrian Belew, Tony Levin, Bill Bruford
ただし, 気をつけなければいけない点があります. つい先ほど, 「en
ロケールのリソースとしては ProgressiveRock_en.properties
ファイルではなく ProgressiveRock_en
クラスのほうが優先される」 と書きましたが, 本当は, 「ProgressiveRock_en.properties
ファイルは参照されず ProgressiveRock_en
クラスのみが参照される」 と言うほうが, 実態に近いです. ですので, 前節に掲げたコードのうち, 次の部分は MissingResourceException
をスローすることになってしまいます.
String elp = progressiveRock.getString("ELP");
そういえば, Properties
クラスの場合ですと, キーに該当する値がなければ単に null
が返って来ますが, ResourceBundle
の場合は MissingResourceException
という実行時例外がスローされる点も, 注意すべきだと思います.
文字列だけではない
プロパティファイルを利用したリソースバンドルでは, キーに対応する値として利用することができるのは単一の文字列のみでした. ListResourceBundle
クラスを継承したリソースクラスなら, それ以外の使い方もできます.
tt4cs.resource.ProgressiveRock_en
を次のように書き換えてみます.
package tt4cs.resource; import java.util.ListResourceBundle; public class ProgressiveRock_en extends ListResourceBundle { @Override protected Object[][] getContents() { return new Object[][] { new Object[] { "ELP", new String[] { "Keith Emerson", "Greg Lake", "Carl Palmer" } } }; } }
この場合, キー "ELP"
に対応する値は, 単一の文字列 (String
) ではなく, 文字列の配列 (String[]
) となります. クライアント側のコードとしては, たとえば次のようになりましょうか.
Object o = progressiveRock.getObject("ELP"); if (o instanceof String[]) System.out.println(Arrays.asList((String[])o));
これを実行しますと, 標準出力には次のように表示されます.
[Keith Emerson, Greg Lake, Carl Palmer]
ふつうのプロパティファイルですと, たとえばコンマ区切りの文字列を文字列の配列に分割 (split) したりとか, あるいは Commons Collection の ExtendedProperties
を代用したりとか, そんな手もありそうです. しかし, もし文字列ではなく, たとえば日付や時刻だとかを値として利用したいような場合, プロパティファイルですとどうしても文字列からパースしてあげなければいけませんし, それなら ListResourceBundle
を利用するほうが楽かもしれないという気もします.
まあ, でも, 通常は国際化のためのリソースをバンドルするために利用するのが ResourceBundle
だよなあという気もしますし, そうなると, どうなんでしょう, ListResourceBundle
はおもしろそうなんだけど使う場面が現実にありうるのかどうか, ちょっと自信がありません.
まとめ
ResourceBundle
に関しては, ほかに ResourceBundle.Control
の話題もあって, そちらも勉強してみるともっといろいろなことができそうな予感がしています. またの機会にやってみたいと思います.
久しぶりにブログを書くというのは, けっこう疲れますね.
No comments:
Post a Comment