|
Preferences API |
||
ちょっとしたデータを使いたいけど... |
||
ちょっとしたデータだけど、セーブやロードしたいものって結構ありませんか。 例えば、フレームの位置と大きさとか。ゲームのハイスコアとか。別にセーブしておかなくてもいいんだけど、できればセーブしておきたいですよね。 だからといって、こんなデータのために、わざわざ設定ファイルを作ったり、JNDI (Java Namind and Directory Interface) などを使うのも面倒です。Windows だったらレジストリがこんな用途に使えそうですが、Java から直接レジストリを扱う方法はないので JNI (Java Native Interface) を使って C/C++ のルーチンをコールして、そこからレジストリにアクセスするという方法になってしまいます。これはかなり面倒ですね。 こんなときに役立つのが Preferences API です。 単にデータのセーブとロードをしてくれる API です。でも、自前でデータを格納するためのファイルとかディレクトリサービスなどを準備することなく使うことができるので、とても簡単に使用できます。 さっそく、使ってみましょう。
|
まずは単純にロードとセーブ | ||||||||
とりあえず、ロードとセーブをしてみましょう。 次のアプリケーションは起動したときに何らかの引数を記述するとそれをセーブし、なければロードして表示するものです。
Preferences API のメインとなるクラスは java.util.prefs.Preferences です。このクラスでセーブ、ロードなどのほとんどの Preferences API の機能をまかなっています。Preferences クラスのオブジェクトの生成には何種類か方法がありますが、PrefsTest1 では次のように行っています。
userNodeForPackage メソッドの引数は Class オブジェクトになります。Class オブジェクトにするのは、Preferences をパッケージ単位で管理するためです。詳しくは後で説明します。 それではデータのセーブをしてみましょう。
セーブするときには put メソッドを使います。引数は 2 つあって、両方とも String クラスで、キーと値のペアになります。ちょうど java.util.Map と同じような感じですね。ここではキーに "name" という文字列を使っています。値は name 変数です。 次の行の flush メソッドはストリームなどの使い方と同じように、強制的に書き込み処理を行う場合に使用します。 Map と同じような使い方だとすれば、ロードはやはり get メソッドですね。
Map の get メソッドと異なるのは、第 2 引数があることです。第 1 引数はキーなので Map と同じですが、第 2 引数は値のデフォルト値を指定しておきます。Map クラスはキーに相当する要素がなければ null オブジェクトを帰しますが、Preferences クラスは要素がなければ第 2 引数のデフォルト値を帰すようになっています。 なんだかよく分からないけど、Preferences オブジェクトに put と get をすればデータのセーブとロードを行ってくれるわけです。どこにもファイルなどは出てきません。ほんとにデータは保存されているんでしょうか。さっそく、実行してみましょう。 ただし、今回のサンプルは今までと異なりパッケージを設定しました。PrefsTest クラスのパッケージは jp.gr.java_conf.skrb.prefs です。パッケージの始めはドメインネームの逆順になっています。その後に、アプリケーションをあらわすような名前を使用します。ここでは C:\temp 以下に jp ディレクトをおいてその下に順順にパッケージに相当するディレクトリを作ってから実行しました。 また、java.exe の引数に -cp (もしくは -classpath) を使用してクラスパスを設定しています。今回は特に jar ファイルは作成せず、クラスファイルがあるディレクトリで実行しているので、"." をクラスパスとして登録しています。
なにも引数をつけないで実行すると、Preferences をロードします。ただし、1 回目の実行結果は何も出力されていません。これは、まだデータをセーブしていないので、Preferences にまだ登録されていなく、get メソッドの第 2 引数の値が出力されているからです。 2 回目の実行でデータのセーブしています。一度登録してしまえば、次からはこの値がロードできます。この値は上書きするか、データを削除するまで有効になります。 ここでは例として出しませんでしたが、登録を削除するのは remove メソッドを使用します。
|
ユーザ? システム? |
||||||||||
ここで、ちょっとした実験をしてみましょう。 まず、先ほどの PrefsTest1 を実行してみます。
ちゃんとデータがロードできますね。 ここで、一度ログアウト (Windows だとログオフですね) します。次に、もしできるなら、異なるアカウントでログインしてみてください。 そして、PrefTest1 を実行してデータをロードしてみてください。
結果として、データはロードできませんでした。 どうしてなんでしょう。答えは Preferences はユーザごとに設定できるからなのです。A というアカウントの Preferences と B というアカウントの Preferences は異なるわけです。 ユーザごとに設定ができるので、たとえば、フレームの大きさや位置などは自分の好きな位置に設定でき、この設定は自分だけに有効で他の人には設定変更がおよばないことになります。 でも、ユーザごとではなくてアプリケーションに固有の設定をしたい場合はどうしましょう。たとえば、ゲームのハイスコアはユーザごとに設定するより、アプリケーションで 1 つにしたいですよね。 こんな場合は Preferences オブジェクトの取得方法を変更することで、対応できます。 先ほどの PrefsTest1 では Preferences オブジェクトを userNodeForPackage メソッドを使用して取得しました。
これを systemNodeForPackage メソッドに変更するだけです。
ユーザをシステムに変更することで、アプリケーションごとに Preferences を設定できるようになります。このようにした PrefTest2.java を作りました。
それでは先ほどと同じように一度実行してデータをセーブしてから、異なるアカウントでデータをロードしてみましょう。
異なるアカウント実行しても
となり、ちゃんとデータをロードすることができました。
|
データはどこに |
|||||||||||||||
無事にデータのセーブとロードはできましたが、実際のデータはどこにあるのでしょう。それがわからないと心配でしょうがないかたもいらっしゃるのではないでしょうか。 Preferences は Windows と UNIX では格納の方法が異なります。UNIX ではファイル、Windows ではレジストリでデータを保持しています。ここでは、Windows の場合を説明しましょう。 Windows では Preferences をレジストリに書くのですが、user と system では格納する場所が異なります。user の場合は \\HKEY_USERS\<アカウント名>\Software\JavaSoft\Prefs 以下に格納されます。ログインしている最中は \\HEKY_USERS\<アカウント名> 以下のレジストリは \\HEKY_CURRENT_USER にコピーされるので、こちらを見ても OK です。 system の場合は \\HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs 以下に格納されます。 どのようにレジストリに登録されるのか、ためしに行ってみました。まず、一度も Preferences API を使ったことない場合のレジストリを見てみましょう。レジストリを見るには regedit を使用します。
Preferences API を使っていない状態だと、レジストリには Prefs というキーがあるだけで、値はなにもセットされていません。 ここで、先ほどの PrefsTest1 を実行してみましょう。
すると、レジストリが次のように変化します。
Prefs 以下にパッケージに対応したキーが作成されています。そしてパッケージに対応するところに name というキーが作成され、そこに保存した値が sakuraba 登録されています。 system の場合でも同じように登録が行われます。 ここで注目していただきたいのが、Preferences API では Preferences をパッケージを基にして登録しているということです。プログラム中では単にキーと値しか使っていないのですが、実際にはそれにパッケージ名を付加して、ツリー上にレジストリに登録を行っているのです。 こうすることで、同じキーを使用しても、パッケージが異なれば区別して使うことができます。 したがって、このパッケージツリーを作るために Preferences オブジェクトを生成する userNodeForPackage メソッドや systemNodeForPackage メソッドでは引数に Class オブジェクトを使用していたのです。 今回のサンプルプログラムでパッケージを使用したのはこういう理由があったからです。Preferences API はパッケージがないアプリケーションでも使用することはできますが、できればパッケージを使用して、データを登録するようにしたほうがいいと思います。 アプリケーションの中で Preferences のツリーのどこにデータを登録しているかを知るには absolutePath メソッドを、またuser か system かを知るには isUserNode メソッドを使います。簡単なサンプル PrefsTest3 で試してみてください。
ソースの中の userNodeForPackage メソッドを systemNodeForPackage メソッドに代えて実行してみてください。ちゃんと出力には反映されていると思います。 ところで、PrefsTest1 と PrefsTest2 では userNodeForPackage メソッドと systemNodeForPackage メソッドを使いましたが、これらのメソッドは引数のオブジェクトのパッケージを調べて、どこに登録を行うか決めています。 これらのメソッドを使う以外にも Preferences を取得する方法があります。 それには userRoot メソッドもしくは systemRoot メソッドと node メソッドを組みあわせて使用します。userRoot メソッドは user ノードのルート、レジストリであれば Prefs に相当する Preferences が取得できます。systemRoot メソッドは同様に system ノードのルートを取得できます。Preferences を取得した後に、node メソッドを使うことで、任意の Preferences を取得することができます。 PrefsTest4 はルート以下のすべてのノードを書きだすアプリケーションです。ここでも、userRoot メソッドと node メソッドを組みあわせて使用しています。
userRootメソッドはコンストラクタで使用しています。
userRoot および systemRoot メソッドは引数はありません。戻り値がルートの Preferences オブジェクトになります。ここからパッケージに対応するノードに移動するにするには node メソッドを使用します。node メソッドの引数は String オブジェクトで、移動するノードを記述します。 このとき、パッケージの区切り文字には "/" (スラッシュ) を使います。例えばルートは / だけで、jp.gr.java_conf.skrb.prefs パッケージであれば /jp/gr/java_conf/skrb/prefs になります。ルートがスラッシュだけというのは UNIX のディレクトリの表し方と同じですね。 node メソッドでは引数の一番はじめがスラッシュであれば絶対パス、スラッシュがなければ相対バスになります。 PrefsTest4 はルートからすべてのノードを表示します。現在のノードの下にどのようなノードがあるかを調べるのが Preference クラスの childrenNames メソッドです。ルートの Preferences の直下のノードを次のように調べています。
node メソッドを使って、ノードを変更しているのがもう 1 つの printNode メソッドです。
このメソッドは再帰を用いて、順順に深いノードを調べています。4 から 7 行目でまずノードの名前を表示しています。depth に応じて空白を出力することでノードの深さを表すようにしています。 9 行目で node メソッドを使用して、現在のノードの直下のノードに移動しています。childrenNema メソッドの戻り値は相対パスで表されるので、そのまま node メソッドの引数に使用すると直下のノードに移動することになります。 そして、10 行目の printNode メソッドを再びコールすることで再帰処理を行います。ノードの末端までいけば、childrenNames の戻り値が長さ 0 の配列になるので、それ以上再帰が行われずに戻ってきます。 このプログラムを実行すると次のような出力になりました。まだ、PrefsTest1 などしか Preferences を使っていないので、こんな結果になったのです。
|
もうちょっと、具体的なサンプル |
||||||||||||||||||||
今までは、Preferences API の仕組みを説明するだけのサンプルアプリケーションだったので、もうちょっと具体的に Preferences API が役に立つようなサンプルを作ってみましょう。 たぶんよく使うと思われるフレームの位置とサイズの設定に Preferences を使用してみましょう。
なにはなくとも Preferences オブジェクトがなければ始まらないので、コンストラクタで取得しています。
6 行目の loadPrefs メソッドで Preferences のロード、11 行目の savePrefs メソッドでセーブを行っています。8 行目から 14 行目はフレームを作成して、クローズボタンが押されたときの処理を記述しています。クローズボタンが押されたときは WindowEvent が発生するので、WindowListener インタフェースの windowClosing メソッドを定義し、その中で Preferences のセーブを行っているのです。 16 行目でフレームの位置とサイズを設定しています。locationX, locationY, width, height が Preferences から取得したデータになります。 18 行目はフレームの中に描画するコンポーネントの初期化を行い、19 行目でフレームを表示させます。 さて、それでは Preferences のロードとセーブを見ていきましょう。どちらも、今までと同じように行いますが、ちょっとだけ異なります。今まではキーも値も String でしたが、ここでは値に String 以外のプリミティブ型の値を使用しています。
LOCATION_X などの定数は Preferences のキーにするための定数です。キーに対応する値は locationX 以下の 4 つの int 型の変数です。 loadPrefs メソッドでは、int 型の値を取り出すために Preferences クラスの getInt メソッドを使用しています。同じように savePrefs メソッドでは putInt メソッドを使用して int 型の値を登録しています。このようにプリミティブ型には専用の getXXX / putXXX メソッドが用意されています。
このようなメソッドが用意されていますが、実際のレジストリには int や double でも文字列で登録されています。したがって、文字列 "10" を get し、getInt を使って読み出すようなことも可能です。 それでは、コンパイルして実行してみてください。フレームを移動させたり、サイズを変更してから、クローズボタンか Exit ボタンを押すと Preferences をセーブしてから終了します。次に立ち上げたときには前回の位置とサイズがちゃんとロードされているのが確認できたでしょうか。
|
他のコンピュータに設定を持っていきたい |
||||||
あるコンピュータで使っていたアプリケーションの設定を、他のコンピュータに持っていきたくなることはないですか。設定ファイルを使っているのであればファイルをコピーすればいいのですが、Preferences を使っている場合はどうすればいいでしょうか。 答えは簡単で、Preferences クラスには Preferences をファイルにセーブしたり、ファイルからロードしたりするためのメソッドが用意されています。これを使えば、他のコンピュータにファイルをコピーすれば大丈夫です。 ファイルなどから Preferences をロードするには inportPreferences メソッドを使用します。引数は InputStream クラスで、このストリームから読み込みを行います。 セーブには exportNode メソッドか exportSubtree メソッドを使用します。引数はどちらも OutputStream クラスです。この 2 つのメソッドの違いは現在のノードの下位のノードをエクスポートするかどうかです。exportNode メソッドは自分のノードだけ、exportSubtree メソッドは下位のノードも含めてエクスポートします。 先ほどの PrefsGUISample1 の設定情報をファイルからロード・セーブできるようにしたものが、PrefsGUISample2 です。
PrefsGUISample2 クラスは PrefsGUISample1 クラスを派生させたもので、loadPrefs メソッドと savePrefs メソッドをオーバロードしています。
Preferences を書き出すファイルは config.xml です。ロードする場合は FileInputStream、セーブする場合は FileOutputStream を使用してストリームを生成しています。後は赤で示したように importPreferences メソッドもしくは exportNode メソッドを示しているだけです。 Preferences はどのような形式でファイルに書き込まれているのでしょうか。お気づきだとは思いますが、ファイルの拡張子からして XML ドキュメントです。config.xml はこのようになりました。
プロジェクト名に対応するのは node タグになります。node タグには map タグが含まれており、その中に複数の entry タグがあるという構造になっています。 entry タグには属性として key と value があり、これが Preferences のキーと値に相当します。ですから、この部分を編集すればそれに応じた設定が行われるようになります。 ここで、XML の解説は行いませんが、XML の構造を定義しているものが DTD で、3 行目の DOCTYPE 文でその指定を行っています。DTD をそのままここに示してもあまり意味はないのでだしませんが、Preferences クラスの JavaDoc を見るとでています。もし、自分で 1 から Preferences の XML ファイルを作るならば、参考になると思います。
|
Preferences のイベント |
||||||||||||||
Preferences に登録した値が変更されたときなどにイベントが発生します。今のところつかえるイベントは次の 2 種類です。
使い方は普通のイベントと同じです。PrefsGUISSample3 はフレームを移動させたり、サイズを変えたときに Preferences を変更するようにしています。変更されると PreferenceChangeEvent が発生するので、それを出力するようにしています。
フレームの移動、リサイズを行うと ComponentEvent が発生するので、そのときに Preferences を変更しています。
PrefsGUISample3 クラスは PrefsGUISample1 クラスの派生クラスなので、コンストラクタでは明示的にスーパークラスのコンストラクタをコールしています (2 行目)。 3 行目から後がイベントに関する登録を行っている部分です。 4 から 11 行目が PreferenceChangeEvent のリスナ登録の部分になります。リスナ登録には addPreferenceChangeListerner メソッドを使用します。ここでのイベント処理はイベントから得られる情報を表示しているだけの簡単な処理なので、Anonymous クラスを使用して行っています。 12 から 25 行目がフレームの移動、リサイズの時に発生す ComponentEvent のリスナ登録です。ComponentEvent が発生したら、イベントに応じて、イベント処理用のメソッドをコールしています。 例として x 座標が更新されたときのイベント処理メソッドである updateLocationX を示しました。 処理していることは単純で、putInt メソッドを使用して Preference を変更して、位置を表示している JTextField オブジェクトの値を設定しているだけです。 ぜひ、実行してどのようにイベントが発生しているか確かめてみてください。
|
おまけ |
||
アプレットでも Preferences API を使ってみたいという方もいると思いますが、残念ながらそのままでは使えません。アプレットではセキュリティのためにローカルのリソースにアクセスすることを禁止していますが、Preferences API もこの理由から使うことはできないのです。 もちろん、Signed Applet にして、適切なポリシーを設定すれば使えるのですが、そこまでして使いたいこともあまりないでしょう。Servlet や CGI を使って、サーバ側で設定を保存するようにすれば、Preferences API をアプレットで使うのと同じことが実現できます。 アプレットで使えないのはちょっと残念ですが、それを除けば便利な API なのでぜひ活用してみてください。 今回使用したサンプルはここからダウンロードできます。 参考 URL
(Jul. 2001) |
|