7 Preferences API

Preferences APIにより、アプリケーションでプリファレンスおよび構成データを管理できます。

アプリケーションには、様々なユーザーおよび環境要件に応じた設定および構成データが必要です。java.util.prefsパッケージは、ユーザーおよびシステムの設定や構成データを、アプリケーションが格納および取得する手段を提供します。データは、実装ごとに異なるバッキング・ストアに永続的に保存されます。プリファレンス・ノードは2つの個別のツリーで構成されます。一方はユーザー・プリファレンス用、もう一方はシステム・プリファレンス用です。

設定データを変更するすべてのメソッドは、非同期的に操作できます。各メソッドはただちに復帰し、変更は永続的なバッキング・ストアに転送されます。flushメソッドを使用すれば、バッキング・ストアを強制的に変更できます。

Preferencesクラス内のメソッドは、単一JVM内の複数スレッドによって同時に呼び出されます。このとき、外部同期を行う必要はありません。逐次実行した場合と同じ結果になります。このクラスが複数のJVMによって同時に使用され、設定データが同じバッキング・ストアに格納された場合、データ・ストアは破壊しませんが、設定データの一貫性は保証されません。

Preferences APIとほかのメカニズムとの比較

Preferences APIを導入する前は、プリファレンスおよび構成データを動的に管理するために、Properties APIまたはJava Naming and Directory Interface (JNDI) APIを使用していました。

プリファレンスおよび構成データは、多くの場合、 java.util.Properties APIを使用してプロパティ・ファイルにアクセスし、このファイルに格納していました。ただし、ディスク上でのファイルの位置またはファイルの名前に関する標準は存在しませんでした。このメカニズムを使用する場合、ユーザーの設定データのバックアップ作成や、マシン間のデータ転送が困難になります。さらに、アプリケーション数が増加するにつれて、ファイル名が重複する可能性が高くなります。また、プラットフォームにローカル・ディスクが存在しない場合、またはデータを外部データ・ストア(企業全域にわたるLDAPディレクトリ・サービスなど)に保存するのが望ましい場合には、このメカニズムは役に立ちません。

適用例は少なくなりますが、JNDI APIを使用してディレクトリ・サービスにアクセスし、そこにユーザー設定や構成データを格納する場合もありました。Properties APIと異なり、JNDIでは、任意のデータ・ストアを使用できます(バックエンドの中立性)。JNDIは強力ですが、サイズが比較的大きく、5つのパッケージと83のクラスから構成されます。JNDIには、ディレクトリ名前空間内で設定データを格納する場所、または格納する名前空間に関するポリシーが存在しません。

PropertiesおよびJNDIには、単純で、汎用的な、バックエンドの中立性を持つ設定管理機能はありません。Preferences APIでは、Properties APIの単純さとJNDIのバックエンドの中立性が同時に実現されます。これには、バッキング・データ・ストアにアクセスできない場合でも、名前の重複を回避し、一貫性を保持し、安定性を向上するために必要なポリシーが組み込まれています。

使用上のノート

この項の情報はPreferences API仕様に含まれていません。Preferences APIの使用例を示すために提供されています。

包含クラスのPreferencesオブジェクトの取得

この項の例では、包含クラスに所属するシステムおよびユーザーPreferencesオブジェクトを取得する方法を示します。これらの例は、インスタンス・メソッド内でのみ動作します。

次の例では、ユーザーごとの設定値を取得しています。取得した各設定値には、適切なデフォルトが割り当てられます。これらのデフォルトは、設定値が設定されていない場合、またはバッキング・ストアにアクセスできない場合に返されます。

ここでは、インラインのStringリテラルではなく、static finalフィールドが、キー名(NUM_ROWSおよびNUM_COLS)として使用されています。このようにすると、キー名の入力ミスによる実行時バグが発生する可能性が減少します。

package com.greencorp.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 getPrefs() {
        Preferences prefs = Preferences.userNodeForPackage(Gadget.class);

        int numRows = prefs.getInt(NUM_ROWS, 40);
        int numCols = prefs.getInt(NUM_COLS, 80);

        ...
    }

前述の例では、ユーザーごとの設定値を取得しています。システムごとに1つの値が必要な場合は、getPrefsの1行目を次のように置き換えます。

Preferences prefs = Preferences.systemNodeForPackage(Gadget.class);

staticメソッドのPreferencesオブジェクトの取得

この項の例では、staticメソッドでシステムおよびユーザーPreferencesオブジェクトを取得する方法を示します。

staticメソッド(またはstaticイニシャライザ)内では、次のように、パッケージ名を明示的に指定する必要があります。

static String ourNodeName = "/com/greencorp/widget";
static void getPrefs() {
    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 parsePrefs() {
    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;
}

設計に関するFAQ

この項では、Preferences APIの設計についてよくある質問への回答を示します。

トピック:

Preferences APIには、Properties APIとどのような関連性がありますか。

これは、非常によく使用されてきたPropertiesクラスを置き換える目的で設計されており、軽量性を維持しながら、様々な点を訂正しています。Propertiesを使用するときは、各プロパティ・ファイルのパス名を明示的に指定する必要がありますが、標準の場所や名前の規約はありません。プロパティ・ファイルは壊れやすくなっています。手動で編集できますが、不注意な編集によって簡単に破損します。文字列以外のデータ型のサポートは存在しません。Propertiesは、ファイル・システム以外の永続性メカニズムで簡単に使用することはできません。つまり、Propertiesの機能には拡張性がありません。

Preferences APIには、JNDIとどのような関連性がありますか。

JNDIと同様に、永続的なキー/値データへのバックエンド中立的なアクセスを提供します。ただし、JNDIは、はるかに強力で重量です。JNDIは、強力な機能を必要とする企業アプリケーションに適しています。Preferences APIは、単純で、汎用的な、バックエンド中立性を持つ設定管理機能として設計されているため、Javaアプリケーションの動作をユーザーの要望に合せて簡単に調整でき、実行をまたがって一部の状態を保持できます。

すべてのgetメソッドに対して呼出し側がデフォルトを渡す必要があるのはなぜですか。

これによって、アプリケーション作成者は強制的に相応のデフォルト値を提供することになり、リポジトリが利用できない場合でもアプリケーションは相応の実行機会を得ることになります。

どのメソッドがBackingStoreExceptionをスローするべきかはどのようにして決定されたのですか。

セマンティックスが絶対的にバッキング・ストアとのやり取りを必要としているメソッドだけが、この例外をスローします。通常のアプリケーションでは、このようなメソッドを呼び出す必要はありません。このようなメソッドを呼び出さないかぎり、バッキング・ストアを利用できない場合でもアプリケーションは実行でき、それが設計の明示的な目標でした。

複数のVMによる同時アクセスについて、このAPIがより強力な保証を提供しないのはなぜですか。同様に、このAPIが複数のPreferences更新を単一トランザクションに結合することを許可しないのはなぜですか(オール・オア・ナッシング・セマンティクスで)。

このAPIは基本的な永続データ・ストレージを提供しますが、それはデータベースの代替を意図したものではありません。このAPIを標準の設定/構成リポジトリ上に実装できることが重要で、そのほとんどはデータベースのような保証および機能を提供しません。このようなリポジトリは、このAPIが意図する目的を十分に満たしています。

このAPIが大文字と小文字を区別するキーとノード名を持つのはなぜですか。同様の空間(Microsoft WindowsレジストリやLDAPなど)で使用されるほかのAPIはそうではありません。

Javaプログラミング言語では、大文字と小文字が区別されるStringキーが一般的です。具体的には、それらはPropertiesクラスによって提供されており、このAPIが置き換える予定です。大文字小文字の区別を要求する方法でPropertiesを使用する人は珍しくありません。たとえば、Javaパッケージ名(大文字小文字を区別)がキーとして使用されることがあります。この設計上の意思決定は、大文字小文字を区別するキーを使用するバッキング・ストア上にPreferencesを実装するシステム・プログラマの寿命を複雑にすると認識されていますが、これは受入れ可能な対価と考えられます。はるかに多くのプログラマがそれを実装するよりPreferences APIを使用するようになるでしょう。

このAPIがJava 2 Collections Frameworkを使用しないのはなぜですか。

このAPIは、かなり特定の目的のために設計されており、その目的のために最適化されています。ジェネリック型がないので(JSR-14を参照)、このAPIは典型的なユーザーにとって便利さに欠けます。Map APIに対して強制的に準拠される場合、コンパイル時型安全性が不足します。また、ほかのMap実装との相互運用性が要求されることは想定していません(この想定が間違っているとわかった場合には、アダプタ・クラスを実装することは簡単です)。Preferences APIは設計上、Mapによく似ているため、後者に習熟しているプログラマは前者を使用することは難しくないはずです。

putおよびremoveメソッドが古い値を返さないのはなぜですか。

それらのメソッドは、バッキング・ストアが利用できない場合でも、実行可能であることが望ましいです。それらが古い値を返す必要がある場合、これはできなくなります。さらに、このAPIが一部の一般的なバックエンド・データ・ストア上に実装されている場合は、パフォーマンスが低下します。

このAPIはなぜ保存済みデフォルトを許可するのですか(要求しませんが)。

この機能は、エンタープライズ全体の設定管理を拡張可能かつコスト効率的にするためにエンタープライズ設定に必要ですが、自己管理される単一ユーザー設定では過剰です。

任意の直列化可能オブジェクトを読み書きするメソッドがこのAPIに含まれていないのはなぜですか。

直列化されたオブジェクトは、やや壊れやすくなっています。そのようなプロパティを読み取るプログラムがそれを書き出すプログラムと異なる場合は、オブジェクトが正しくまたはまったく直列化復元されないことがあります。直列化されたオブジェクトをこのAPIを使用して保存することはできますが、推奨しておらず、便利なメソッドも提供していません。

Preferencesがインタフェースでなく、抽象クラスなのはなぜですか。

上位互換性のある方法で新しいメソッドを追加する機能が、Preferencesを「ミックスイン」として使用できないというデメリットを上回ると判断されました。つまり、任意のクラスをPreferencesオブジェクトとして使用することもできません。また、これによって、staticメソッド用の別個のクラスの必要性がなくなります。インタフェースにstaticメソッドを含めることはできません。

デフォルトのバッキング・ストアはどこにありますか。

システムおよびユーザー・プリファレンス・データは、実装ごとに異なるバッキング・ストアに永続的に保存されます。たとえば、フラット・ファイル、OS固有のレジストリ、ディレクトリ・サーバー、SQLデータベースなどのバッキング・ストアに格納されます。たとえば、Windowsシステムでは、データはWindowsレジストリに格納されます。

Linuxシステムでは、システム・プリファレンスは通常、ネットワーク・インストールのjava-home/.systemPrefsに格納されるか、ローカル・インストールの/etc/.java/.systemPrefsに格納されます。両方とも存在する場合は、/etc/.java/.systemPrefsが優先されます。システム・プロパティjava.util.prefs.systemRootを設定して、システム・プリファレンスの場所をオーバーライドできます。ユーザー・プリファレンスは通常、user-home/.java/.userPrefsに格納されています。システム・プロパティjava.util.prefs.userRootを設定して、ユーザー・プリファレンスの場所をオーバーライドできます。