設定データを変更するすべてのメソッドは、非同期的に操作できます。各メソッドはただちに復帰し、変更は永続的なバッキング・ストアに転送されます。flushメソッドを使用すれば、バッキング・ストアを強制的に更新できます。
Preferencesクラス内のメソッドは、単一JVM内の複数スレッドによって同時に呼び出されます。このとき、外部同期を行う必要はありません。逐次実行した場合と同じ結果になります。このクラスが複数のJVMによって同時に使用され、設定データが同じバッキング・ストアに格納された場合、データ・ストアは破壊しませんが、設定データの一貫性は保証されません。
詳細については、次のリンクから選択してください。
Preferences APIを導入する以前は、設定および構成データを個別に管理するために、これから説明するProperties APIまたはJNDI APIを使用していました。
設定および構成データは、多くの場合、java.util.Properties APIを使用してプロパティ・ファイルにアクセスし、このファイルに格納していました。ただし、ディスク上でのファイルの位置またはファイルの名前に関する標準は存在しませんでした。このメカニズムを使用する場合、ユーザーの設定データのバックアップ作成や、マシン間のデータ転送が困難になります。アプリケーション数が増加するにつれて、ファイル名が重複する可能性が高くなります。また、ローカル・ディスクが存在しない場合、またはデータを外部データ・ストア(企業全域にわたるLDAPディレクトリ・サービスなど)に保存するのが望ましい場合には、このメカニズムは使用できません。
適用例は少なくなりますが、JNDI (Java Naming and Directory Interface) APIを使用してディレクトリ・サービスにアクセスし、そこにユーザー設定や構成データを格納する場合もありました。Properties APIと異なり、JNDIでは、任意のデータ・ストアを使用できます(バックエンドの中立性)。JNDIは強力ですが、サイズが比較的大きく、5つのパッケージと83のクラスから構成されます。JNDIには、ディレクトリ名前空間内で設定データを格納する場所、または格納する名前空間に関するポリシーが存在しません。
PropertiesおよびJNDIには、単純で、汎用的な、バックエンドの中立性を持つ設定管理機能はありません。Preferences APIでは、Properties APIの単純さとJNDIのバックエンドの中立性が同時に実現されます。これには、バッキング・データ・ストアにアクセスできない場合でも、名前の重複を回避し、一貫性を保持し、安定性を向上するために必要なポリシーが組み込まれています。
このセクションでは、Preferences APIの仕様を説明するのではなく、Preferences APIの使用例をいくつか示します。
次の例では、包含クラスに所属するPreferencesオブジェクト(システムおよびユーザー)を取得する方法を示します。これらの例は、インスタンス・メソッド内でのみ動作します。
ここでは、インラインのStringリテラルではなく、static finalフィールドが、キー名(NUM_ROWS
およびNUM_COLS
)として使用されています。このようにすると、キー名の入力ミスによる実行時バグが発生する可能性が減少します。
取得した各設定値には、適切なデフォルトが割り当てられます。これらのデフォルトは、設定値が設定されていない場合、またはバッキング・ストアにアクセスできない場合に返されます。
package com.acme.widget; import java.util.prefs.*; public class Gadget { // Preference keys for this package private static final String NUM_ROWS = "num_rows"; private static final String NUM_COLS = "num_cols"; void foo() { Preferences prefs = Preferences.userNodeForPackage(Gadget.class); int numRows = prefs.getInt(NUM_ROWS, 40); int numCols = prefs.getInt(NUM_COLS, 80); ... } }
上の例では、ユーザーごとの設定値を取得しています。システムごとの単一値が必要な場合は、foo
の最初の行を次の行に置き換えます。
Preferences prefs = Preferences.systemNodeForPackage(Gadget.class);
前のセクションでは、包含クラスに所属するPreferencesオブジェクトを取得し、インスタンス・メソッドの内部を操作しました。staticメソッド(またはstaticイニシャライザ)内では、次のように、パッケージ名を明示的に指定する必要があります。
Static String ourNodeName = "/com/acme/widget"; static void foo() { Preferences prefs = Preferences.userRoot().node(ourNodeName); ... }
通常は、システム設定オブジェクトをstaticイニシャライザ内で1回取得し、システム設定が必要なときにそれを使用します。
static Preferences prefs = Preferences.systemRoot().node(ourNodeName);
通常は、ユーザー設定オブジェクトの場合も、同じ方法を適用します。ただし、コードをサーバー内で使用し、複数のユーザーが同時にまたは順番に実行する場合は、この方法は使用しません。このようなシステムでは、userNodeForPackage
およびuserRoot
が呼出し側のユーザーに対して適切なノードを返します。つまり、userNodeForPackage
またはuserRoot
の呼出しは、適切なスレッドから適切なタイミングに行うことが重要になってきます。このようなサーバー環境でコードを使用する場合は、前の例のように、使用する直前にユーザー設定オブジェクトを取得することをお薦めします。
Preferences APIには、複数の設定が不可分に変更されるトランザクションに似たデータベースはありません。ただし、複数の設定を変更するときは、1単位で行う必要があります。たとえば、x座標とy座標を格納して、そこにウィンドウを配置することを想定します。不可分に変更するには、これらの値を単一の設定に格納します。さまざまな方法でエンコードできます。ここでは簡単な例を示します。
int x, y; ... prefs.put(POSITION, x + "," + y);
このような複合設定を読み込むときは、復号化する必要があります。安定性を確保するために、値が破損した(解析不可能な)場合を考慮することをお勧めします。
static int X_DEFAULT = 50, Y_DEFAULT = 25; void baz() { String position = prefs.get(POSITION, X_DEFAULT + "," + Y_DEFAULT); int x, y; try { int i = position.indexOf(','); x = Integer.parseInt(coordinates.substring(0, i)); y = Integer.parseInt(position.substring(i + 1)); } catch(Exception e) { // Value was corrupt, just use defaults x = X_DEFAULT; y = Y_DEFAULT; } ... }
標準のアプリケーション・コードでは、バッキング・ストアを利用できるかどうかに関する情報は必要ありません。ほとんどの場合、バッキング・ストアは常に利用できます。利用できない場合でも、バッキング・ストア内の設定値のかわりにデフォルト値を使用して、実行を継続するはずです。一部の高度なプログラムでは、バッキング・ストアを利用できない場合に、動作を変更する(または単純に実行を拒否する)ことができます。次のメソッドでは、バッキング・ストアを利用できるかどうかを判断するために、値を変更した設定をバッキング・ストアにフラッシュしています。
private static final String BACKING_STORE_AVAIL = "BackingStoreAvail"; private static boolean backingStoreAvailable() { Preferences prefs = Preferences.userRoot().node("<temporary>"); try { boolean oldValue = prefs.getBoolean(BACKING_STORE_AVAIL, false); prefs.putBoolean(BACKING_STORE_AVAIL, !oldValue); prefs.flush(); } catch(BackingStoreException e) { return false; } return true; }