[目次] [前項目] [次項目]
Java オブジェクトが、直列化を使って状態をファイルに保管したり、塊としてデータベースに保管したりする場合、そのデータを読み込むクラスのバージョンがそのデータを書き込んだバージョンと異なる可能性があります。
バージョン化には、クラスの同一性に関し、いくつかの根本的な問題があります。たとえば、互換性のある変更とは何か、という問題があります。互換性のある変更とは、クラスとその呼び出し元との間の約束ごとに影響を与えない変更です。
このセクションでは、目標、前提条件、そして解決策について記述します。この解決策は、変更できるものを制限し、メカニズムを慎重に選択することによって、この問題に対処しようとするものです。
ここで示す解決策では、フィールドの追加やクラスの追加によって発展するクラスを「自動的に」処理するメカニズムを示します。直列化では、バージョン化は、バージョンごとにクラス固有のメソッドを実装することなく行われます。ストリーム形式は、クラス固有のメソッドを呼び出すことなく処理(トラバース)されます。
目標は次のとおりです。
- 異なる仮想マシンで稼働する異なるバージョンのクラス間における双方向の通信を、次の方法でサポートする。
- Javaクラスが、同じクラスの古いバージョンで書き込まれたストリームを読み込めるようなメカニズムを定義する。
- Javaクラスが、同じクラスの古いバージョンで読み込まれることを意図したストリームを書き込めるようなメカニズムを定義する。
- 持続性と RMI のデフォルトの直列化を提供する
- 簡単なケースでは性能がよく、簡潔なストリームが作成され、それによって RMI で直列化が使用できる。
- ストリームを書き込むのに使われたクラスと全く同じクラスを識別し、ロードできる。
- バージョン化しないクラスに対しオーバーヘッドを低く保つ。
- ストリームに保管されているオブジェクト固有のメソッドを呼び出さずにストリームの処理(トラバーサル)が可能なストリーム形式を使用する。
前提条件は次のとおりです。
- 直列化可能クラスは、そのストリーム形式を制御して、その目標を達成する必要があるので、バージョン化は直列化クラスだけに適用される。外部化可能クラスは、外部形式に結合されるそれ独自のバージョン化を行う。
- すべてのデータやオブジェクトは、書き込まれた順序でストリームから読み込まれたり、そこでスキップされたりしなければならない。
- クラスは、個別に発展したり、スーパー型やサブ型と協調して発展する。
- クラスは名前で識別される。同じ名前の 2 つのクラスが異なるバージョンであったり、全く異なるクラスであったりすることがある。この違いは、それぞれのインタフェースや、それぞれのインタフェースのハッシュを比較すれば区別することができる。
- デフォルトの直列化では、型の変換は行われない。
- ストリーム形式では、線形順序の型変更だけをサポートすればよく、型の任意の分岐をサポートする必要はない。
クラスの展開において、非展開クラスによって設定された約束ごとを維持するのは、展開された(後のバージョンの)クラスの責任です。これは、2 つの形をとります。まず、展開されたクラスは、元のバージョンによって与えられたインタフェースに関する既存の前提条件を壊すことはできません。それによって、展開されたクラスを元のクラスの代わりに使用することができます。次に、元の(または前の)バージョンと通信するとき、展開されたクラスは、以前のバージョンが非展開クラスの約束を引き続き満たせるだけの、十分で同等な情報を与えなければなりません。
ここで説明した目的のために、各クラスは、そのスーパー型によって定義されたインタフェースまたは約束を実装し、拡張します。クラスの新しいバージョン、たとえば、foo'は、fooのための約束を引き続き満足しなければなりませし、インタフェースを拡張したり、その実装を修正したりすることができます。
直列化を介したオブジェクト間の通信は、それらのインタフェースによって定義される約束には含まれていません。直列化は、実装間の私的なプロトコルです。各実装がそのクライアントによって期待される約束を満たせるように十分通信することは、その実装の責任です。
Java言語仕様 の第 13 章に、Java クラスが展開するときのこれらのバイナリ互換の説明があります。バイナリ互換の柔軟性のほとんどは、クラス、インタフェース、フィールド、メソッドなどの名前の記号参照を、遅い段階でバインドすることに起因しています。
直列化されたオブジェクトストリームのバージョン化に対する設計の基本的な点を、下に示します。
- デフォルトの直列化メカニズムは、ストリームのフィールドと、仮想マシンの対応するクラスのフィールドとをバインドするのに記号モデルを使用する。
- ストリームの参照される各クラスは、それ自身、そのスーパー型、およびそのストリームに書き込まれた各 nonstatic と nontransient フィールドの型と名前を固有に識別する。これらのフィールドは、フィールド名によってソートされたプリミティブ型のものが最初にきて、次にフィールド名でソートされたオブジェクトフィールドが続く。
- ストリームにはクラスごとに 2 つの型のデータが存在する。つまり、必須データ(オブジェクトの nonstatic と nontransient フィールドに直接対応する)と任意データ(プリミティブとオブジェクトの任意のシーケンスからなる)である。クラス全体でも、必須の部分でも、任意の部分でも、必要ならスキップできるように、必須データと任意データをストリームにどのように指定するかは、ストリーム形式によって定義される。
- 必須データは、クラス記述子によって定義された順序の、オブジェクトのフィールドからなる。
- 任意データは、ストリームに書き込まれ、クラスのフィールドとは直接対応しない。この任意情報の長さ、型、バージョン化はクラスそのものの責任である。
- writeObject/readObject メソッドがクラスに対して定義されていれば、それらが、そのクラスの状態を書き込み/読み込みするデフォルトのメカニズムに代わって使用される。これらのメソッドは、クラスに対する任意データの書き込みと読み込みを行う。必須データは、
defaultWriteObject を呼び出すことによって書き込まれ、defaultReadObject を呼び出すことによって読み込まれる。
- 各クラスのストリーム形式は、ストリーム固有識別子 (SUID)によって識別される。デフォルト値はそのクラスのハッシュである。そのクラスのそれより後のすべてのバージョンは、それらと互換性のあるストリーム固有識別子 (SUID)を宣言しなければならない。これにより、同じ名前をもつ複数のクラスが、単一クラスのバージョンであると間違って認識されることはない。
- ObjectOutputStream と ObjectInputStream のサブ型は、annotateClass メソッドを使って、クラスを識別する独自の情報をもつことができる。たとえば、MarshalOutputStream には、クラスの URL が組み込まれている。
この概念を使えば、展開するクラスのいろいろなケースに対し、設計上どのように対応するかを説明することができます。これらのケースは、クラスのいずれかのバージョンによって書き込まれたストリームの観点から記述されます。ストリームが同じクラスの同じバージョンで読み込まれた場合、情報や機能が失われることはありません。このストリームは、元のクラスに関する唯一の情報源です。そのクラス記述は、それが元のクラス記述のサブセットである限り、そのストリームのデータと、再構成されるクラスのバージョンを一致させるのに十分な情報です。
これらの記述は、クラスの以前のバージョンか以後のバージョンを再構成するためにストリームを読み込む、という観点からのものです。RPC システムの用語でいえば、これは「受取り側が正しくする」システムです。書き込み側は、そのデータを最も適した形式で書き込みますので、受取り側は、その情報を解釈して必要な部分を抽出し、入手できない部分を補う必要があります。
クラスに対する互換性のない変更とは、相互運用性の保証が維持できないような変更です。クラスの展開の過程で起こる互換性のない変更には、次のものがあります。- フィールドを削除する。クラスのフィールドが削除されると、書き込まれたストリームにはその値がない。そのストリームが以前のクラスによって読み込まれると、ストリームに値がないため、そのフィールドの値はデフォルト値に設定される。しかし、このデフォルト値は、以前のバージョンがその約束を果たす能力を損なうことがある。
- 階層においてクラスを上方または下方に移動する。ストリームのデータ順序が正しくなくなるため、この変更はできない。
- nonstatic フィールドを static に、または nontransient フィールドを transient に変更する。これは、フィールドをクラスから削除するのと同じことである。そのクラスのこのバージョンでは、そのデータはストリームに書き込まれないので、そのクラスの以前のバージョンで読むことはできない。フィールドの削除と同じように、以前のバージョンのフィールドはデフォルト値に初期化されるので、そのクラスは予期できないエラーとなることがある。
- プリミティブフィールドの宣言された型を変更する。クラスの各バージョンは、データをその宣言された型で書き込む。ストリームのデータの型はフィールドの型と一致しないので、クラスの以前のバージョンがそのフィールドを読み込もうとするとエラーになる。
- writeObject や readObject メソッドを変更して、デフォルトのフィールドデータを書き込んだり、読み込んだりしないようにする。または、それらを変更して、前のバージョンがそのデータを書き込んだり読み込んだりしなかった場合、書き込んだり読み込んだりしようとする。デフォルトのフィールドデータがストリームにあるかないかは、一貫していなければならない。
- クラスを Serializable から Externalizable へ変更したり、その反対にするのは、互換性のない変更である。こうすると、そのストリームに、使用できるクラスの実装と互換性のないデータが入ることになる。
- Serializable や Externalizable を取り除くのは、互換性のない変更である。こうすると、書き込まれたときに、そのクラスの古いバージョンで必要なフィールドが除外されることになる。
クラスへの互換性のある変更は次のように処理されます。
- フィールドを追加する。再構成されるクラスにストリームにないフィールドがあると、オブジェクトのそのフィールドはその型に対するデフォルト値に初期化される。クラス固有の初期化が必要なら、そのクラスは readObject メソッドによって、そのフィールドをデフォルト値以外に設定することができる。
- クラスを追加する。ストリームには、ストリームにおける各オブジェクトの型階層がある。ストリームのこの階層と現行クラスを比較すれば、追加のクラスがわかる。ストリームには、そのオブジェクトを初期化するために使用できる情報はないので、そのクラスのフィールドはデフォルト値に初期化される。
- クラスを除く。ストリームのクラス階層と現行クラスのそれを比較すれば、クラスが削除されたことがわかる。この場合、そのクラスに対応するフィールドとオブジェクトが、ストリームから読み込まれる。プリミティブフィールドは破棄されるが、削除されたクラスによって参照されるオブジェクトは作成される。こうするのは、それらが後にストリームで参照される可能性があるからである。それらのオブジェクトは、ストリームがガベージコレクトされたり、リセットされたりするときに、ガベージコレクトされる。
- writeObject/readObject メソッドを追加する。ストリームを読み込むバージョンにこれらのメソッドがあるなら、デフォルトの直列化によってストリームに書き込まれた必須データは、通常どおり readObject によって読み込まれなければならない。このメソッドは、任意データを読み込む前に、まず
defaultReadObject を呼び出す必要がある。writeObject メソッドは、通常どおり、defaultWriteObject を呼び出して必須データを書き込まなければならない。その後、任意データを書き込むことができる。
- writeObject/readObject メソッドを取り除く。このストリームを読み込むクラスにこれらのメソッドがないと、必須データはデフォルトの初期化によって読み込まれ、任意データは破棄される。
- java.io.Serializable を追加する。これは、型を追加するのと同じことである。ストリームにはこのクラスに対する値がないので、そのフィールドは、デフォルト値に初期化される。直列化不能クラスのサブクラス化をサポートするには、そのクラスのスーパー型に 引数無しの構築子があり、そのクラス自身がデフォルト値に初期化されなければならない。引数無しの構築子がないと、NotSerializableException がスローされる。
- java.io.Serializable を取り除き、Serializable でないようにする。これは、クラスを取り除くのと同じことである。これは、そのクラスのデータを読み込み、破棄することによって行われる。
- フィールドへのアクセスを変更する。アクセス修飾子 public、package、protected、private を変更しても、直列化によってそれらのフィールドに値を代入できることには影響しない。
- フィールドを static から nonstatic へ、または transient から nontransient へ変更する。これは、フィールドをクラスに追加するのと同じことである。新しいフィールドはストリームに書き込まれるが、その値は以前のクラスによって無視される。これは、直列化によって static や transient のフィールドに値が代入されないためである。
[目次] [前項目] [次項目]
Copyright (C) 1996, 1997 Sun Microsystems, Inc. All rights
reserved.