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