Java

Preferences API

ドキュメントの目次

概要

アプリケーションには、さまざまなユーザおよび環境要件に応じた設定および構成データが必要です。java.util.prefs パッケージを使用すれば、ユーザおよびシステムの設定および構成データを、アプリケーションから格納および取得することができます。このデータは、実装ごとに異なるバッキングストアに持続的に保存されます。設定ノードは 2 つの個別のツリーで構成されます。一方はユーザ設定用、もう一方はシステム設定用です。

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

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

詳細については、以下のリンクから選択してください。


Preferences API と他の機構との比較

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 オブジェクトを取得する

次の例では、包含クラスに所属する 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(this);

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

        ...
    }
}
上の例では、ユーザごとの設定値を取得しています。システムごとの値だけが必要な場合は、foo の最初の行を次の行に置き換えます。
        Preferences prefs = Preferences.systemNodeForPackage(this);

Static メソッドの Preferences オブジェクトを取得する

前の節では、包含クラスに所属する Preferences オブジェクトを取得し、インスタンスメソッドの内部を操作しました。Static メソッド (または静的初期化子) 内では、次のように、パッケージ名を明示的に指定する必要があります。
    Static String ourNodeName = "/com/acme/widget";

    static void foo() {
        Preferences prefs = Preferences.userRoot().node(ourNodeName);

        ...
    }
通常は、システム設定オブジェクトを初期化子内で 1 回取得し、システム設定が必要なときにそれを使用します。
    static Preferences prefs =  Preferences.systemRoot().node(ourNodeName);
通常は、ユーザ設定オブジェクトの場合も、同じ方法を適用します。ただし、コードをサーバ内で使用し、複数のユーザが同時にまたは順番に実行する場合は、この方法は使用しません。このようなシステムでは、userNodeForPackage および userRoot が呼び出し側のクライアントに対して適切なノードを返します。つまり、userNodeForPackage または userRoot への呼び出しは、適切なスレッドから適切なタイミングに行うことが重要になってきます。このようなサーバ環境でコードを使用する場合は、使用する直前にユーザ設定オブジェクトを取得することをお勧めします。
「包含クラスの Preferences オブジェクトを取得する」を参照してください。

不可分な更新

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("");
        try {
            boolean oldValue = prefs.getBoolean(BACKING_STORE_AVAIL, false);
            prefs.putBoolean(BACKING_STORE_AVAIL, !oldValue);
            prefs.flush();
        } catch(BackingStoreException e) {
            return false;
        }
        return true;
    }

Java コレクション API の設計に関する FAQ

ここでは、Preferences API の設計に関する FAQ をまとめてあります。
  1. Preferences API には、Properties API とどのような関連性がありますか。

    Preferences API は、Properties クラスの使用頻度の高いプロパティを置き換える目的で設計されており、軽量さを維持しながら、さまざまな点を訂正しています。Properties API を使用するときは、各プロパティファイルのパス名を明示的に指定する必要があります。ただし、プロパティファイルの標準の場所または名前の規約はありません。プロパティファイルは任意に編集できますが、破壊しやすいため、慎重に編集する必要があります。Properties API では、文字列以外のデータ型は使用できません。Properteis API の持続性を保持するには、ファイルシステムを使用する必要があります。これらのことから、Properties API には拡張性はありません。

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

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

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

    アプリケーションに適切なデフォルト値を渡すことによって、リポジトリが利用できない場合でも、アプリケーションはそのデフォルト値で実行することができます。

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

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

  5. 複数の VM による同時アクセスを、この API でより強力にサポートしないのはなぜですか。同様に、複数の設定の更新を結合して単一トランザクションに組み込み、すべて更新するかまったく更新しないセマンティクスを適用しないのはなぜですか。

    この API は、持続性のある基本的なデータ記憶域として使用し、データベースの代わりとしては使用しません。この API は、標準の設定/構成リポジトリ上に実装できるようにすることが重要です。これらのリポジトリのほとんどでは、データベースのような機能は提供していません。これらのリポジトリは、この API の設計目的を満たしています。

  6. この API のキーとノード名では、なぜ大文字と小文字が区別されるのですか。同様の環境で動作する他の API (Microsoft Windows Registry、LDAP など) では、区別されていません。

    Java プログラミング言語では、大文字と小文字が区別される String キーが一般的です。特に、String キーは、Properties クラスによって提供されます。この API は、Properties クラスを置き換える目的で設計されています。Properties を使用するときに、大文字と小文字を区別することもあります。たとえば、Java パッケージ名をキーとして使用するときに、大文字と小文字を区別することがあります。キーの大文字と小文字を区別すると、そのキーを使用してバッキングストア上に Preferences を実装する作業が複雑になります。しかし、Preferences API でプログラミングするプログラマが増えていけば、実装工数の増加は吸収できると考えられます。

  7. この API で Java 2 Collections Framework が使用されないのはなぜですか。

    この API は、特定の目的に合わせて設計および最適化されています。この API では汎用データ型を使用できません ( JSR-14 を参照)。このため、標準的なユーザにとっては多少使いにくいかもしれません。Map API への準拠が適用されている場合でも、コンパイル時に型保証しません。また、他の Map 実装との相互運用性は想定していません。ただし、相互運用性が必要な場合は、アダプタクラスを実装すれば対応できます。しかし、Preferences API は、Map と類似した設計になっているため、Map を習熟していれば簡単に使用できます。

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

    put および remove 系のメソッドは、バッキングストアが利用できない場合でも、実行できなければなりません。これらのメソッドから古い値を返す必要がある場合は、この要件に対応できなくなります。また、この API を一般的なバックエンドデータストア上に実装した場合に、パフォーマンスが低下することがあります。

  9. この API の保存済みデフォルトはどんな目的で使用しますか。また、なぜ必須ではないのですか。

    保存済みデフォルトは、企業設定に必要な機能です。つまり、企業全体の設定を管理するときの拡張性および費用効率を向上させることができます。しかし、単一ユーザの設定をユーザ自身が管理する場合には、過剰な機能です。

  10. 任意の直列化可能オブジェクトの読み取り/書き込みを行うメソッドが、この API に組み込まれていないのはなぜですか。

    直列化可能オブジェクトは、壊れやすいオブジェクトです。このようなプロパティを読み取るプログラムは、プロパティを書き出すプログラムと異なる場合は、直列化可能オブジェクトが正しくまたはまったく直列化復元されないことがあります。直列化可能オブジェクトは、この API を使用して格納できます。ただし、この方法は推奨していないうえ、対応するメソッドも用意していません。

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

    新しいメソッドを追加するときに上方互換性を保証することは、Preferences を「mixin」として使用できなくなる短所 (任意のクラスを Preferences オブジェクトとして機能させることができない) を上回ると判断しました。また、static メソッド用のクラスを個別に用意する必要がなくなります。インタフェースには、static メソッドを組み込むことはできません。


Copyright © 2002 Sun Microsystems, Inc.All Rights Reserved.

Sun
Java ソフトウェア