設定データを変更するすべてのメソッドは、非同期的に操作できます。各メソッドはただちに復帰し、変更は永続的なバッキングストアに転送されます。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 は強力な API ですが、サイズが比較的大きく、5 つのパッケージと 83 のクラスから構成されます。JNDI には、ディレクトリ名前空間内で設定データを格納する場所、または格納する名前空間に関するポリシーが存在しません。
Properties および JNDI には、単純で、汎用的な、バックエンドの中立性を持つ設定管理機能はありません。Preferences API では、Properties API の単純さと JNDI のバックエンドの中立性が同時に実現されます。Preferences API には、バッキングデータストアにアクセスできない場合でも、名前の重複を回避し、一貫性を保持し、安定性を向上するために必要なポリシーが組み込まれています。
このセクションでは、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; }