このドキュメントでは、直列化可能レコードをサポートするためのJavaオブジェクト直列化仕様の変更について説明します。レコード・クラス機能の概要は、JEP 395を参照してください。
変更は、JOSSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
1 - システム・アーキテクチャ
1.1 概要
JavaTMオブジェクトを保存して取り出せることは、もっともtransientなものを除くすべてのアプリケーションを作成するために不可欠です。直列化された形式でのオブジェクトを保存して取り出すために重要なことは、オブジェクトを再構築するために十分なオブジェクトの状態を表現することです。ストリームに保存されるオブジェクトは、Serializable
またはExternalizable
インタフェースのどちらかをサポートできます。JavaTMオブジェクトの場合は、直列化された形式が、オブジェクトの内容が保存されたJavaTMクラスを識別および検証し、その内容を新しいインスタンスに復元できなければいけません。直列化可能オブジェクトの場合、ストリームには、ストリーム内のフィールドを互換バージョンのクラスに復元するために十分な情報が取り込まれます。Externalizableオブジェクトの場合、クラスが内容の外部フォーマットに責任を持ちます。
保存および取り出すオブジェクトは、ほかのオブジェクトを参照していることがよくあります。これらのほかのオブジェクトは、オブジェクト間の関係を保持するために、同時に保存および取り出される必要があります。オブジェクトが保存されると、そのオブジェクトから到達可能なすべてのオブジェクトも保存されます。
JavaTMオブジェクトの直列化の目標は次のとおりです。
- シンプルで拡張性のあるメカニズムにする。
- JavaTMオブジェクトの型および安全属性を直列化形式でも保持する。
- リモート・オブジェクトの必要に応じて、整列化と非整列化をサポートできるようにする。
- JavaTMオブジェクトのシンプルな永続性をサポートできるようにする。
- クラスごとの実装を要求する(カスタマイズ目的のみ)。
- オブジェクトが外部フォーマットを定義できる。
1.2 オブジェクト・ストリームへの書き込み
このサブセクションの小幅な書き換え
オブジェクトやプリミティブをストリームに書き込むことは、簡単な処理です。次に例を示します。
// Serialize today's date to a file.
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();
まず、OutputStream
(この場合はFileOutputStream
)が、バイトを受け取るために必要です。次に、FileOutputStream
に書き込むObjectOutputStream
が作成されます。そして、文字列「Today」とDateオブジェクトがストリームに書き込まれます。より汎用的に言えば、オブジェクトはwriteObject
メソッドによって書き込まれ、プリミティブはDataOutput
のメソッドによってストリームに書き込まれます。
writeObject
メソッド(セクション2.3「writeObjectメソッド」を参照)は、指定されたオブジェクトを直列化し、オブジェクト・グラフ内の他のオブジェクトへの参照を再帰的にトラバースして、グラフの完全に直列化された表現を作成します。ストリーム内で、オブジェクトへの最初の参照では、オブジェクトが直列化または外部化され、そのオブジェクトのハンドルが割り当てられます。そのオブジェクトへのそれ以後の参照は、ハンドルとしてエンコードされます。オブジェクト・ハンドルを使用しても、オブジェクト・グラフ内で当然発生する共有参照や循環参照は保持されます。オブジェクトのそれ以後の参照はハンドルだけを使用するため、非常に簡潔な表現が可能になります。ハンドルの使用により、オブジェクト・グラフ内で自然に発生するオブジェクトの共有が保持され、オブジェクト間の循環参照(つまり、グラフ内での循環)が可能になります。
配列、enum定数、およびClass
、ObjectStreamClass
、String
型のオブジェクトには、特別の処理が必要です。その他のオブジェクトは、ストリームに保存したり、そこから取り出したりするためのSerializable
またはExternalizable
インタフェースを実装しなければいけません。
プリミティブ・データ型は、DataOutput
インタフェースのメソッド(writeInt
、writeFloat
、writeUTF
など)でストリームに書き込まれます。個別のバイトとバイト配列は、OutputStream
のメソッドで書き込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードでストリームに書き込まれ、各レコードの前にはマーカーとレコード内のバイト数が示されます。
ObjectOutputStream
を拡張して、ストリームのクラスに関する情報をカスタマイズしたり、直列化されるオブジェクトを置き換えることができます。詳細は、annotateClass
とreplaceObject
メソッドの説明を参照してください。
1.3 オブジェクト・ストリームからの読み取り
書き込みと同様、ストリームからのオブジェクトの読み込みも、簡単なことです。
// Deserialize a string and date from a file.
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
まず、ソース・ストリームとして、InputStream
(この場合はFileInputStream
)が必要です。次に、InputStream
から読み込むObjectInputStream
が作成されます。そして、文字列「Today」とDateオブジェクトがストリームから読み込まれます。より汎用的に言えば、オブジェクトはreadObject
メソッドで読み込まれ、プリミティブはDataInput
のメソッドによってストリームから読み込まれます。
readObject
メソッドは、ストリーム内の次のオブジェクトを直列化復元し、他のオブジェクトへの参照を再帰的にトラバースして、直列化されたオブジェクトの完全なグラフを作成します。
プリミティブ・データ型は、DataInput
インタフェースのメソッド(readInt
、readFloat
、readUTF
など)から読み込まれます。個別のバイトとバイト配列は、InputStream
のメソッドで読み込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードから読み込まれます。
ObjectInputStream
を拡張して、クラスに関するストリーム内のカスタマイズされた情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳細は、resolveClass
とresolveObject
メソッドの説明を参照してください。
1.4 コンテナとしてのオブジェクト・ストリーム
オブジェクト直列化は、1つまたは複数のプリミティブやオブジェクトを含むバイト・ストリームを作成し、消費します。ストリームに書き込まれたオブジェクトは、ほかのオブジェクト(これもストリーム内で表現されている)を順に参照します。オブジェクト直列化は、含まれているオブジェクトをエンコードおよび保存するストリーム・フォーマットを1つだけ作成します。
コンテナとして作用する各オブジェクトは、プリミティブやオブジェクトを保存したり取り出したりできるインタフェースを実装します。これらのインタフェースは、ObjectOutput
およびObjectInput
インタフェースであり、次のことを行います。
- 書込み先のストリームや読込み元のストリームを提供する
- プリミティブ型やオブジェクトをストリームに書き込む要求を処理する
- プリミティブ型やオブジェクトをストリームから読み込む要求を処理する
ストリームに保存される各オブジェクトは、保存できることを明示的に示さなければならず、状態の保存と復元に必要なプロトコルを実装しなければいけません。オブジェクト直列化は、そのようなプロトコルを2つ定義しています。これらのプロトコルによって、コンテナは、オブジェクトの状態を書き込んだり、読み込んだりすることをオブジェクトに依頼できます。
オブジェクト・ストリームに保存されるには、各オブジェクトはSerializable
またはExternalizable
インタフェースを実装しなければいけません。
Serializable
クラスの場合、オブジェクト直列化によって、オブジェクトの各クラスのフィールドが自動的に保存および復元され、フィールドやスーパー・タイプを追加することで展開するクラスが自動的に処理されます。直列化可能クラスは、どのフィールドが保存または復元されるかを宣言し、オプション値やオブジェクトの書き込みや読込みを行うことができます。Externalizable
クラスの場合、オブジェクト直列化は、その外部フォーマット、およびスーパー・タイプの状態がどのように保存および復元されるかの完全な制御をクラスに委譲します。
1.5 クラスの直列化可能フィールドの定義
クラスの直列化可能フィールドは、2つの方法で定義できます。クラスのデフォルト直列化可能フィールドは、非transientおよび非staticフィールドとして定義されます。このデフォルト計算は、Serializable
クラスに特別なフィールドserialPersistentFields
を宣言することによってオーバーライドできます。このフィールドは、直列化可能フィールドの名前および型を一覧表示するObjectStreamField
オブジェクトの配列を使って、初期化する必要があります。フィールドの修飾子は、private、static、およびfinalにする必要があります。フィールドの値がnullであるか、ObjectStreamField[]
のインスタンスではない場合、またはフィールドが必須の修飾子を持たない場合は、フィールドがまったく宣言されていない場合と同じ動作になります。
たとえば、次の宣言は、デフォルトの動作を複製します。
class List implements Serializable {
List next;
private static final ObjectStreamField[] serialPersistentFields
= {new ObjectStreamField("next", List.class)};
}
serialPersistentFields
を使用してクラスのSerializableフィールドを定義することによって、直列化可能フィールドはSerializable
クラスの現在の定義内のフィールドでなければいけないという制限がなくなります。Serializable
クラスのwriteObject
およびreadObject
メソッドは、セクション1.7「クラスの直列化可能フィールドへのアクセス」に記述されているインタフェースを使用して、クラスの現在の実装をクラスの直列化可能フィールドにマッピングできます。したがって、Serializable
クラスのフィールドはあとのリリースで変更できます(リリース境界をまたがって互換性を持つ必要があるSerializableフィールドへのマッピングを保持しているかぎり)。
ノート: ただし、このメカニズムを使用して内部クラスの直列化可能フィールドを指定する場合には、制限があります。内部クラスは、定数または定数から成る式に初期化された、final staticフィールドのみ含むことができます。結果として、内部クラスにserialPersistentFields
は設定できません(ただし、staticメンバー・クラスには設定できます)。内部クラス・インスタンスの直列化に関するその他の制限事項については、セクション1.10「Serializableインタフェース」を参照してください。
1.6 クラスの直列化可能フィールドおよびデータの文書化
Serializableクラスの代替実装との相互運用性を可能にするためにクラスの直列化可能状態をドキュメント化し、クラス展開をドキュメント化することは重要です。直列化可能フィールドをドキュメント化することは、フィールドが直列化可能かどうかを確認する最終機会を与えることになります。javadoc直列化タグ、@serial
、@serialField
、および@serialData
は、ソース・コード内のSerializableクラスの直列化形式をドキュメント化する方法を提供します。
デフォルト直列化可能フィールドの
@serial
タグは、javadocコメント内に置くべきです。構文は以下のとおりです:@serial
field-descriptionオプションのfield-descriptionには、フィールドの意味と設定可能な値を記述します。field-descriptionが複数行にまたがることも可能です。初期リリース後にフィールドを追加した場合は、フィールドが追加されたバージョンを@sinceタグを使用して示します。@serial
のfield-descriptionは、直列化固有のドキュメントを提供し、直列化形式ドキュメント内でフィールドのjavadocコメントに付加されます。serialPersistentFields
配列のObjectStreamField
コンポーネントをドキュメント化するには、@serialField
タグを使います。各ObjectStreamField
コンポーネントで、これらのタグのうちの1つを使うべきです。構文は以下のとおりです:@serialField
field-name field-type field-description@serialData
タグは、書き込みまたは読込みが行われるデータの順序および型を記述します。このタグは、writeObject
によって書き込まれたオプション・データ、またはExternalizable.writeExternal
メソッドによって書き込まれたすべてのデータの順序と型を記述します。構文は以下のとおりです:@serialData
data-description
javadocアプリケーションは、javadoc直列化タグを認識し、各SerializableクラスおよびExternalizableクラスの仕様を生成します。これらのタグの使用例については、セクションC.1「java.io.File代替実装の例」を参照してください。
クラスをSerializableとして宣言した場合、オブジェクトの直列化可能状態は、直列化可能フィールド(名前と型による)に加え、オプション・データによって定義されます。オプション・データは、Serializable
クラスのwriteObject
メソッドによってのみ明示的に書き込むことができます。オプション・データはSerializable
クラスのreadObject
メソッドによって読み込むことができ、直列化は読み込まれないオプション・データをスキップします。
クラスをExternalizableとして宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるように、および以前のバージョンで読み込まれたデータを書き込めるように、独自の展開を処理する必要があります。クラスは、データの保存および復元時には、スーパー・クラスと連携しなければいけません。ストリーム内のスーパークラス・データの位置を指定する必要があります。
Serializableクラスの設計者は、クラスに保存される情報が永続性に適していて、相互運用性および展開のために直列化固有の規則に従っていることを保証する必要があります。クラスの展開の詳細は、第5章「直列化可能オブジェクトのバージョン管理」を参照してください。
1.7 クラスの直列化可能フィールドへのアクセス
直列化は、ストリーム内の直列化可能フィールドにアクセスするための2つのメカニズムを提供します。
- デフォルトのメカニズムはカスタマイズが不要
- SerializableフィールドAPIを使って、クラスは名前と型によって直列化可能フィールドを明示的にアクセス/設定できる
Serializable
インタフェースを実装し、それ以上のカスタマイズを行わないオブジェクトを読み込みまたは書き込むときには、デフォルトのメカニズムが自動的に使われます。直列化可能フィールドは、クラスの対応するフィールドにマッピングされ、値はそれらのフィールドからストリームに書き込まれるか、または読み込まれてそれぞれ割り当てられます。クラスがwriteObject
およびreadObject
メソッドを提供する場合は、defaultWriteObject
およびdefaultReadObject
を呼び出すことによってデフォルト・メカニズムを呼び出すことができます。writeObject
およびreadObject
メソッドが実装されているときは、直列化可能フィールド値が書き込まれる前または読み込まれたあとに、クラスはそれらを変更する機会を持ちます。
デフォルト・メカニズムを使用できない場合は、直列化可能クラスは、ObjectOutputStream
のputFields
メソッドを使って、直列化可能フィールドの値をストリームに置くことができます。ObjectOutputStream
のwriteFields
メソッドは、値を正しい順序で置いてから、直列化の既存のプロトコルを使ってストリームにそれらの値を書き込みます。同様に、ObjectInputStream
のreadFields
メソッドは、ストリームから値を読み込み、クラスが名前で(かつ任意の順序で)それらを利用できるようにします。直列化可能フィールドAPIの詳細は、セクション2.2「ObjectOutputStream.PutFieldクラス」およびセクション3.2「ObjectInputStream.GetFieldクラス」を参照してください。
1.8 ObjectOutputインタフェース
ObjectOutput
インタフェースは、オブジェクト・ストレージへのabstractストリーム・ベース・インタフェースを提供します。このインタフェースはDataOutputインタフェースの拡張なので、それらのメソッドを使ってプリミティブ・データ型を書き込むことができます。このインタフェースを実装するオブジェクトを使えば、プリミティブやオブジェクトを格納できます。
package java.io;
public interface ObjectOutput extends DataOutput
{
public void writeObject(Object obj) throws IOException;
public void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
public void flush() throws IOException;
public void close() throws IOException;
}
The
writeObject
メソッドは、オブジェクトを書き込むために使用します。スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージに書き込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが破損している可能性があります。このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
1.9 ObjectInputインタフェース
ObjectInput
インタフェースは、オブジェクト取り出しへのabstractストリーム・ベース・インタフェースを提供します。DataInput
インタフェースの拡張なので、このインタフェースのプリミティブ・データ型を読み込むメソッドを利用できます。
package java.io;
public interface ObjectInput extends DataInput
{
public Object readObject()
throws ClassNotFoundException, IOException;
public int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
public long skip(long n) throws IOException;
public int available() throws IOException;
public void close() throws IOException;
}
readObject
メソッドは、オブジェクトを読み込んで返すために使用します。スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージから読み込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが破損している可能性があります。このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
1.10 Serializableインタフェース
オブジェクトの直列化を行うと、保存されるオブジェクトのJavaTMクラスに関する情報を持つストリームが作成されます。直列化可能オブジェクトの場合、これらのオブジェクトを復元するのに十分な情報が保持されます(クラスの異なる(しかし互換性のある)バージョンの実装が存在している場合でも)。Serializable
インタフェースは、直列化可能プロトコルを実装するクラスを識別するために定義されます。
package java.io;
public interface Serializable {};
Serializableクラスの要件は以下のとおりです。
java.io.Serializable
インタフェースを実装します直列化可能にするべきフィールドを特定する
(
serialPersistentFields
メンバーを使ってフィールドを明示的に直列化可能と宣言するか、transientキーワードを使って非直列化可能フィールドを指定する)最初の直列化可能でないスーパー・クラスの、引数のないコンストラクタにアクセスできる
このクラスは、オプションで次のメソッドを定義できます。
writeObject
メソッド - どの情報を保存するか制御したり、追加情報をストリームに付加したりするreadObject
メソッド - 対応するwriteObject
メソッドで書き込まれた情報を読み込んだり、復元後のオブジェクトの状態を更新したりするwriteReplace
メソッド - クラスが、ストリームに書き込まれる置換オブジェクトを指定できる(詳細は、セクション2.5「writeReplaceメソッド」を参照してください。)
readResolve
メソッド - クラスが、ストリームから読み込まれたばかりのオブジェクトの置換オブジェクトを指定できる(詳細は、セクション3.7「readResolveメソッド」を参照してください。)
ObjectOutputStream
およびObjectInputStream
を使用すると、操作対象の直列化可能クラスを展開できます(以前のバージョンのクラスとの互換性を持つクラスへの変更が可能)。互換性を保つ変更に使用できるメカニズムの詳細は、セクション5.5「互換性のあるJavaの型展開」を参照してください。
ノート: ローカル・クラスおよび匿名クラスを含む内部クラス(staticメンバー・クラスではないネストされたクラス)の直列化は、いくつかの理由により、使用しないことをお薦めします。非staticコンテキストで宣言された内部クラスには、内包するクラス・インスタンスへの暗黙的な非transient参照が含まれるので、そのような内部クラス・インスタンスを直列化すると、関連する外部クラス・インスタンスも直列化することになります。内部クラスを実装するためにjavac
(またはその他のJavaTMコンパイラ)によって生成された合成フィールドは、実装に依存するので、コンパイラによって相違が生じることがあります。そのようなフィールドの相違により、デフォルトのserialVersionUID
値が競合するのみでなく、互換性が損なわれる可能性があります。ローカルおよび匿名の内部クラスに割り当てられる名前も実装に依存するので、コンパイラによって相違が生じる可能性があります。内部クラスは、コンパイル時定数フィールド以外のstaticメンバーを宣言できないので、serialPersistentFields
メカニズムを使用して直列化可能フィールドを指定できません。さらに、外部インスタンスに関連付けられた内部クラスは、引数なしのコンストラクタ(そのような内部クラスのコンストラクタは、内包するインスタンスを付加パラメータとして暗黙的に受け入れる)を持たないため、Externalizable
を実装できません。ただし、前述の問題はいずれも、staticメンバー・クラスには当てはまりません。
1.11 Externalizableインタフェース
Externalizableオブジェクトの場合、オブジェクトのクラスの識別情報のみがコンテナによって保存されます。クラスが内容を保存して復元する必要があります。Externalizable
インタフェースは、次のように定義されます。
package java.io;
public interface Externalizable extends Serializable
{
public void writeExternal(ObjectOutput out)
throws IOException;
public void readExternal(ObjectInput in)
throws IOException, java.lang.ClassNotFoundException;
}
Externalizableオブジェクトのクラスの要件は、以下のとおりです。
java.io.Externalizable
インタフェースを実装しますwriteExternal
メソッドを実装してオブジェクトの状態を保存する(その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)
readExternal
メソッドを実装して、writeExternal
メソッドによって書き込まれたデータをストリームから読み取って、オブジェクトの状態を復元する(その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)
writeExternal
とreadExternal
メソッドのみがフォーマットに責任を持つ(外部で定義されたフォーマットが書き込まれる場合)ノート:
writeExternal
およびreadExternal
メソッドはpublicであり、クライアントが、メソッドとフィールドを使用せずにオブジェクト内で情報を書き込んだり読み込んだりできる危険性があります。これらのメソッドを使うのは、オブジェクトが保持する情報が機密でないとき、またはそれを公開してもセキュリティ・リスクが発生しないときだけにしなければいけません。publicで引数なしのコンストラクタを持つ
ノート: 内包するインスタンスに関連付けられた内部クラスは、引数なしのコンストラクタを持つことができません。そのようなクラスのコンストラクタは、内包するインスタンスを付加パラメータとして暗黙的に受け入れるためです。したがって、
Externalizable
インタフェース・メカニズムを内部クラスに使用できません。内部クラスを直列化する必要がある場合には、Serializable
インタフェースを実装するようにしてください。ただし、直列化可能な内部クラスの場合でも、いくつかの制限事項があります。その詳細は、セクション1.10「Serializableインタフェース」を参照してください。
Externalizableクラスは、オプションで次のメソッドを定義できます。
writeReplace
メソッド - クラスが、ストリームに書き込まれる置換オブジェクトを指定できる(詳細は、セクション2.5「writeReplaceメソッド」を参照してください。)
readResolve
メソッド - クラスが、ストリームから読み込まれたばかりのオブジェクトの置換オブジェクトを指定できる(詳細は、セクション3.7「readResolveメソッド」を参照してください。)
1.12 Enum定数の直列化
enum定数の直列化は、通常の直列化可能または外部化可能オブジェクトとは異なります。enum定数の直列化された形式を構成するのは、その名前のみです。定数のフィールド値は形式内に存在しません。enum定数を直列化するために、ObjectOutputStream
はenum定数のname
メソッドで返される値を書き込みます。enum定数を直列化復元するために、ObjectInputStream
はストリームから定数名を読み取ります。直列化復元された定数は、定数のenum型と受け取った定数名を引数として渡すjava.lang.Enum.valueOf
メソッドを呼び出すことで取得されます。
他の直列化可能または外部化可能オブジェクトと同様に、enum定数は直列化ストリームにその後出現する後方参照のターゲットとして機能できます。
enum定数を直列化するプロセスはカスタマイズできません。enum型で定義されたクラス固有のwriteObject
、readObject
、readObjectNoData
、writeReplace
、readResolve
メソッドは、直列化および直列化復元の間は無視されます。同様に、serialPersistentFields
またはserialVersionUID
フィールド宣言もすべて無視されます。すべてのenum型は0L
の固定されたserialVersionUID
を持ちます。送信されるデータの型にはバリエーションがないため、enum型の直列化可能なフィールドおよびデータをドキュメント化する必要はありません。
1.13 レコードの直列化
これは新しいサブセクションです
レコードは、一般的な直列化可能オブジェクトまたは外部化可能なオブジェクトとは異なる方法で直列化されます。レコード・オブジェクトの直列化形式は、レコード・コンポーネントから導出された値のシーケンスです。レコード・オブジェクトのストリーム形式は、ストリーム内の一般的なオブジェクトの形式と同じです。直列化復元中、指定したストリーム・クラス記述子の等価ローカル・クラスがレコード・クラスである場合、最初にストリーム・フィールドが読み取られて再構築され、レコードのコンポーネント値として機能します。次に、コンポーネント値(または、コンポーネント値がストリーム内に存在しない場合はコンポーネントの型のデフォルト値)を引数として使用してレコードの正規コンストラクタを呼び出すことにより、レコード・オブジェクトが作成されます。
他の直列化可能または外部化可能オブジェクトと同様に、レコード・オブジェクトは直列化ストリームにその後出現する後方参照のターゲットとして機能できます。ただし、コンポーネントによって直接または過渡的にレコード・オブジェクトが参照されているグラフ内の循環は保持されません。レコード・コンポーネントはレコード・コンストラクタの呼出しより前に直列化復元されるため、このような制限になります(セクション1.14「循環参照」を参照してください)。
レコード・オブジェクトが直列化または外部化されるプロセスをカスタマイズすることはできません。直列化または外部化中、レコード・クラスによって定義されたクラス固有のwriteObject
、readObject
、readObjectNoData
、writeExternal
およびreadExternal
メソッドは無視されます。ただし、直列化対象の代替オブジェクトまたは指定した置換が、それぞれwriteReplace
およびreadResolve
メソッドによって指定される場合があります。serialPersistentFields
フィールド宣言はすべて無視されます。レコード・クラスのために直列化可能フィールドおよびデータを記述する必要はありません。なぜなら、代替オブジェクトと置換オブジェクトのどちらが使用されるか以外に、シリアル形式に違いはないからです。レコード・クラスのserialVersionUID
は、明示的に宣言されなていないかぎり、0L
です。レコード・クラスについてのserialVersionUID値の照合に関する要件は免除されます。
1.14 循環参照
これは新しいサブセクションです
セクション1.2「オブジェクト・ストリームへの書き込み」で説明されているとおり、ハンドルの使用により、オブジェクト・グラフ内で発生する循環参照を保持できます。
例証のみを目的とした最小限に人為的な例:
class Data implements Serializable {
private static final long serialVersionUID = ...
Object obj;
}
class Carrier implements Serializable {
private static final long serialVersionUID = ...
private final Data d;
public Carrier(Data d) { this.d = d; }
public Data d() { return d; }
}
// create an instance of both Data and Carrier, and a cycle between them
Data d1 = new Data();
Carrier c1 = new Carrier(d1);
d1.obj = c1;
// serialize
ObjectOutputStream oos = new ObjectOutputStream(...);
oos.writeObject(c1);
// deserialize
ObjectInputStream ois = new ObjectInputStream(...);
Carrier c2 = (Carrier) ois.readObject();
オブジェクトc2
は、直列化復元されると、d
にData
のインスタンスを参照させます。その結果として、そのobj
フィールドにc2
の同じインスタンスを参照し戻させます。c2
によって参照されるオブジェクトのアイデンティティは、c2.d().obj
によって参照されるオブジェクトのアイデンティティと同一です(つまり、c2 == c2.d().obj
)。
オブジェクトc2
の割当て、およびそのハンドルの割当ては、フィールド値(セクション3.1「ObjectInputStreamクラス」のステップ12を参照)の再構築の前に行われます。これにより、直列化復元中にフィールド値(および再帰的にそのフィールド値)がc2
のハンドルを参照できるようになります。この方法により、一般的なオブジェクトの直列化復元でオブジェクト・グラフの循環をサポートします。
ここで、次のように、Carrier
がレコード・クラスであったらどうなるか考えてみます。
record Carrier(Data d) implements Serializable { }
オブジェクトc2
は、直列化復元されると、d
にData
のインスタンスを参照させます。その結果として、そのobj
フィールドに(c2
を参照するのではなく) null
を参照させます。直列化復元中、元のオブジェクト・グラフ内のd.obj
を介した循環参照は保持されません。
レコード・オブジェクトc2
の割当て、およびそのハンドルの割当ては、フィールド値(つまり、将来のレコードのコンポーネント値: セクション3.1「ObjectInputStreamクラス」のステップ11を参照)の再構築の後に行われます。レコード・コンポーネント値が再構築される前に一連の既知のオブジェクトにレコード・オブジェクトのハンドルが追加される場合、その初期値はnull
になります。ハンドルがレコード・オブジェクトに割り当てられるのは、レコード・オブジェクトが(正規コンストラクタの呼出しを介して)構築された後のみです。この結果、レコード・コンポーネント値の直列化復元中、レコード・オブジェクトのハンドルに対するストリーム内の参照では、初期値はnull
になります。したがって、直列化復元中、そのコンポーネント(または推移的にそのフィールド)のレコード・オブジェクトを対象とする循環は保持されません。
1.135 機密情報の保護
リソースへの制御アクセスを提供するクラスを開発する場合には、機密性の高い情報と機能が保護されるように注意しなければいけません。直列化復元の際、オブジェクトのprivate状態が復元されます。たとえば、ファイル記述子には、オペレーティング・システム・リソースへのアクセスを提供するハンドルが含まれています。状態の復元はストリームから行われるので、ファイル記述子を偽造できるということは、何らかの不法なアクセスが可能だということです。したがって、直列化の実行時には無難なアプローチを取る必要があり、ストリームにオブジェクトの有効な表現だけが含まれているとは信じないでください。クラスのセキュリティの低下を回避するために、オブジェクトの機密状態がストリームから復元されないようにする必要があり、またはクラスによって再検証される必要があります。クラスの機密データを保護するにはいくつかの技法があります。
もっとも簡単な技法は、機密データを含むフィールドをprivate transientとすることです。transientフィールドは、永続的ではなく、永続性メカニズムによって保存されません。フィールドをこのようにすると、その状態がストリームに現われず、直列化復元の際にも復元されません。(privateフィールドの)書き込みや読込みをクラスの外部で行うことはできないので、クラスのtransientフィールドは安全です。
特に機密性の高いクラスは、一切直列化すべきではありません。これを実現するには、オブジェクトはSerializable
やExternalizable
インタフェースを実装するべきではありません。
クラスによっては、書き込みや読込みは許可するけれども、直列化復元の際に状態を明示的に処理して再検証する方が便利なこともあります。クラスは、適切な状態だけを保管および復元するwriteObject
およびreadObject
メソッドを実装すべきです。アクセスを拒否すべき場合には、NotSerializableException
をスローすることで、それ以上のアクセスを防ぎます。
2 - オブジェクト出力クラス
2.1 ObjectOutputStreamクラス
追加ステップが追加されました
クラスObjectOutputStream
は、オブジェクト直列化を実装します。このクラスは、すでに直列化されたオブジェクト・セットを含めて、ストリームの状態を保持します。そのメソッドは、指定されたオブジェクトとそれらが参照するオブジェクトを保存するために、直列化するオブジェクトのトラバーサルを制御します。
package java.io;
public class ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants
{
public ObjectOutputStream(OutputStream out)
throws IOException;
public final void writeObject(Object obj)
throws IOException;
public void writeUnshared(Object obj)
throws IOException;
public void defaultWriteObject()
throws IOException, NotActiveException;
public PutField putFields()
throws IOException;
public writeFields()
throws IOException;
public void reset() throws IOException;
protected void annotateClass(Class cl) throws IOException;
protected void writeClassDescriptor(ObjectStreamClass desc)
throws IOException;
protected Object replaceObject(Object obj) throws IOException;
protected boolean enableReplaceObject(boolean enable)
throws SecurityException;
protected void writeStreamHeader() throws IOException;
public void write(int data) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
public void flush() throws IOException;
protected void drain() throws IOException;
public void close() throws IOException;
public void writeBoolean(boolean data) throws IOException;
public void writeByte(int data) throws IOException;
public void writeShort(int data) throws IOException;
public void writeChar(int data) throws IOException;
public void writeInt(int data) throws IOException;
public void writeLong(long data) throws IOException;
public void writeFloat(float data) throws IOException;
public void writeDouble(double data) throws IOException;
public void writeBytes(String data) throws IOException;
public void writeChars(String data) throws IOException;
public void writeUTF(String data) throws IOException;
// Inner class to provide access to serializable fields.
abstract static public class PutField
{
public void put(String name, boolean value)
throws IOException, IllegalArgumentException;
public void put(String name, char data)
throws IOException, IllegalArgumentException;
public void put(String name, byte data)
throws IOException, IllegalArgumentException;
public void put(String name, short data)
throws IOException, IllegalArgumentException;
public void put(String name, int data)
throws IOException, IllegalArgumentException;
public void put(String name, long data)
throws IOException, IllegalArgumentException;
public void put(String name, float data)
throws IOException, IllegalArgumentException;
public void put(String name, double data)
throws IOException, IllegalArgumentException;
public void put(String name, Object data)
throws IOException, IllegalArgumentException;
}
public void useProtocolVersion(int version) throws IOException;
protected ObjectOutputStream()
throws IOException;
protected writeObjectOverride()
throws NotActiveException, IOException;
}
単一引数のObjectOutputStream
コンストラクタは、指定されたOutputStream
にオブジェクトを直列化するObjectOutputStream
を作成します。このコンストラクタは、writeStreamHeader
を呼び出して、マジック番号とバージョンをストリームに書き込みます。このストリームは、単一引数のObjectInputStream
コンストラクタの対応するreadStreamHeader
呼出しによって読み取られ、検査されます。セキュリティ・マネージャがインストールされている場合、このコンストラクタは、"enableSubclassImplementation"
SerializablePermission
がputFields
メソッドまたはwriteUnshared
メソッド、あるいはその両方をオーバーライドするサブクラスのコンストラクタによって直接または間接に呼び出されたときに、これをチェックします。
writeObject
メソッドは、オブジェクトをストリームに直列化するために使用します。オブジェクトは、次のように直列化されます。
サブクラスが実装をオーバーライドする場合は、
writeObjectOverride
メソッドを呼び出してから、復帰します。実装のオーバーライドは、このセクションの最後で説明します。ブロック・データ・バッファにデータがあれば、それがストリームに書き込まれ、バッファがリセットされます。
オブジェクトがnullであれば、nullがストリームに置かれ、
writeObject
は復帰します。ステップ8で説明されているように、オブジェクトが以前に置き換えられていれば、置換えのハンドルをストリームに書き込み、
writeObject
は復帰します。オブジェクトがストリームにすでに書き込まれていれば、そのハンドルがストリームに書き込まれ、
writeObject
は復帰します。オブジェクトが
Class
であれば、対応するObjectStreamClass
がストリームに書き込まれ、そのクラスのハンドルが割り当てられ、writeObject
は復帰します。オブジェクトが
ObjectStreamClass
である場合、オブジェクトにハンドルが割り当てられ、その後、セクション4.3「直列化された形式」で説明されているクラス記述子形式の1つを使用してストリームに書き込まれます。Version 1.3以降のJava 2 SDK, Standard Editionでは、writeClassDescriptor
メソッドがObjectStreamClass
(ダイナミック・プロキシ・クラス以外のクラスを表す場合。関連付けられたClass
オブジェクトをjava.lang.reflect.Proxy
のisProxyClass
メソッドに渡すことによって判別される)を出力するために呼び出されます。その後、表されるクラスの注釈が書き込まれます。クラスがダイナミック・プロキシ・クラスであれば、annotateProxyClass
メソッドが呼び出され、そうでない場合はannotateClass
メソッドが呼び出されます。writeObject
メソッドが戻ります。オブジェクトのクラスまたは
ObjectInputStream
のサブクラス(あるいはその両方)による潜在的な置換を処理します。オブジェクトのクラスがenum型でなく、また適切な
writeReplace
メソッドを定義する場合は、そのメソッドが呼び出されます。置換オブジェクトを戻して直列化することもできます。enableReplaceObject
メソッドの呼出しによりreplaceObject
メソッドが有効に設定されている場合、このメソッドを呼び出すことにより、直列化中のオブジェクトをObjectOutputStream
のサブクラスで置換できます。前のステップで元のオブジェクトを置き換えた場合は、置換オブジェクトでreplaceObject
メソッドが呼び出されます。
上記の一方または両方のステップで元のオブジェクトを置き換えた場合、元のオブジェクトから置換オブジェクトへのマッピングが(ステップ4で利用するために)記録されます。その後、新規オブジェクトに対し、ステップ3から7が繰り返されます。
置換オブジェクトが、ステップ3から7を適用できない型の場合、ステップ10で置換オブジェクトを使った処理が再開されます。
オブジェクトが
java.lang.String,
の場合、文字列は長さの情報として書き込まれ、その情報のあとにModified UTF-8でエンコードされた文字列の内容が続きます。詳細は、セクション6.2「ストリーム要素」を参照してください。ハンドルが文字列に割り当てられ、writeObject
が戻ります。オブジェクトが配列の場合、
writeObject
が再帰的に呼び出されて、配列のObjectStreamClass
が書き込まれます。配列用のハンドルが割り当てられます。そのあとに配列の長さが続きます。その後、配列の各要素がストリームに書き込まれて、writeObject
が戻ります。オブジェクトがenum定数であれば、
writeObject
を再帰的に呼び出すことによって、enum型の定数のObjectStreamClass
が書き込まれます。ストリーム内では、はじめて参照されるときのみ出現します。enum定数のハンドルが割り当てられます。次に、ステップ9で説明したように、enum定数のname
メソッドで返された値がString
オブジェクトとして書き込まれます。ストリーム内にすでに同じ名前の文字列が出現した場合には、それへの逆参照が書き込まれます。writeObject
メソッドが戻ります。
オブジェクトがレコード・オブジェクトである場合、レコード・オブジェクトのクラスの
ObjectStreamClass
は、writeObject
を再帰的に呼び出すことによって書き込まれます。ストリーム内では、はじめて参照されるときのみ出現します。レコード・オブジェクトには、ハンドルが割り当てられます。レコード・オブジェクトのコンポーネントは、ストリームに書き込まれます。
a. If the record object is serializable or externalizable, the record components are written, as if by invoking the `defaultWriteObject` method. b. If the object is neither serializable or externalizable, the `NotSerializableException` is thrown.
writeObject
メソッドが戻ります。
123.正規オブジェクトの場合、writeObject
を再帰的に呼び出すことによって、オブジェクトのクラスのObjectStreamClass
が書き込まれます。ストリーム内では、はじめて参照されるときのみ出現します。このオブジェクト用のハンドルが割り当てられます。
The contents of the object are written to the stream.
a. If the object is serializable, the highest serializable class is
located. For that class, and each derived class, that class's fields
are written. If the class does not have a `writeObject` method, the
`defaultWriteObject` method is called to write the serializable fields
to the stream. If the class does have a `writeObject` method, it is
called. It may call `defaultWriteObject` or `putFields` and
`writeFields` to save the state of the object, and then it can write
other information to the stream.
b. If the object is externalizable, the `writeExternal` method of the
object is called.
c. If the object is neither serializable or externalizable, the
`NotSerializableException` is thrown.
例外は、トラバーサル中に発生する場合も、基になるストリーム内で発生する場合もあります。IOException
のサブクラスの場合、例外プロトコルを使って例外がストリームに書き込まれ、ストリーム状態が破棄されます。最初の例外をストリームに書き込んでいる間に2番目のIOException
がスローされた場合、ストリームは不明状態のままになり、writeObject
からStreamCorruptedException
がスローされます。その他の例外の場合、ストリームは中止され、不明で使用不能な状態のままになります。
writeUnshared
メソッドは、「非共有」オブジェクトをObjectOutputStream
に書き込みます。このメソッドはwriteObject
と同様ですが、指定されたオブジェクトを常にストリーム内で一意な新しいオブジェクトとして書き込みます(以前に直列化されたインスタンスを指す逆参照としてではなく)。具体的には、次のようになります。
writeUnshared
を使用して書き込まれたオブジェクトは、そのオブジェクトが以前に書き込まれているかどうかにかかわらず、常に新しく出現するオブジェクト(ストリームにまだ書き込まれたことがないオブジェクト)として同じ方法で直列化されます。writeUnshared
によって以前に書き込まれたオブジェクトを書き込む際にwriteObject
を使用すると、以前のwriteUnshared
操作は、別のオブジェクトの書き込みとして扱われます。つまり、ObjectOutputStream
は、writeUnshared
呼出しによって書き込まれたオブジェクト・データへの逆参照を生成しません。
writeUnshared
を介してオブジェクトを書き込むこと自体は、直列化復元されたときにそのオブジェクトへの一意参照を保証するものではありませんが、1つのオブジェクトがストリーム内で複数回定義されることは可能になるので、受け取り側がObjectInputStream.readUnshared
メソッド(セクション3.1「ObjectInputStreamクラス」を参照)を複数回呼び出しても競合しません。ここで説明した規則は、writeUnshared
によって書き込まれた基本レベルのオブジェクトだけに適用され、直列化されるオブジェクト・グラフ内で推移的に参照されるサブオブジェクトには適用されません。
defaultWriteObject
メソッドは、現在のクラスに対するデフォルト直列化メカニズムを実装します。このメソッドの呼出しは、クラスのwriteObject
メソッドからのみ可能です。このメソッドは、現在のクラスのすべての直列化可能フィールドをストリームに書き込みます。writeObject
メソッドの外部からこのメソッドが呼び出されると、NotActiveException
がスローされます。
putFields
メソッドは、ストリーム内の直列化可能フィールドの値を設定する際に呼出し側が使用するPutField
オブジェクトを返します。フィールドは、任意の順序で設定できます。すべてのフィールドの設定が完了したら、writeFields
を呼び出して、フィールド値を正規の順序でストリームに書き込む必要があります。フィールドが設定されていない場合、その型に適したデフォルト値がストリームに書き込まれます。このメソッドは、直列化可能クラスのwriteObject
メソッド内からしか呼び出すことができません。また、このメソッドは、1回しか呼び出すことができず、defaultWriteObject
がすでに呼び出されている場合は呼び出せません。writeFields
を呼び出したあとでないと、ほかのデータをストリームに書き込むことはできません。
reset
メソッドは、ストリーム状態をリセットして、構築時の状態に戻します。Reset
は、すでにストリームに書き込まれたオブジェクトの状態を破棄します。ストリーム内の現在位置にリセットのマークが付けられるので、対応するObjectInputStream
も同じ位置でリセットされます。以前にストリームに書き込まれたオブジェクトが、ストリームに書込み済みのオブジェクトとして記憶されることはありません。これらのオブジェクトは、ストリームに再度書き込まれます。これは、オブジェクトの内容やオブジェクトを再送信しなければいけない場合に有用です。Reset
は、オブジェクトの直列化中は呼び出されない場合もあります。不正に呼び出されると、IOException
がスローされます。
Java 2 SDK, Standard Edition, v1.3から、ObjectStreamClass
の直列化が必要になると、writeClassDescriptor
メソッドが呼び出されるようになりました。writeClassDescriptor
は、ObjectStreamClass
表現の直列化ストリームへの書込みを担当します。サブクラスでこのメソッドをオーバーライドすることにより、クラス記述子の直列化ストリームへの書込み方法をカスタマイズできます。このメソッドをオーバーライドする場合は、ObjectInputStream
内の対応するreadClassDescriptor
メソッドもオーバーライドして、カスタム・ストリーム表現からクラス記述子を再構成することをお薦めします。デフォルトでは、writeClassDescriptor
は、セクション6.4「ストリーム形式の文法」で指定された形式に従ってクラス記述子を書き込みます。このメソッドは、ObjectOutputStream
が古い直列化ストリーム形式を使用していない場合にのみ、呼出し可能です(セクション6.3「ストリーム・プロトコル・バージョン」を参照)。この直列化ストリームが古い形式(ObjectStreamConstants.PROTOCOL_VERSION_1
)を使用している場合、クラス記述子はオーバーライドまたはカスタマイズが不可能な方法で内部的に書き込まれます。
Class
の直列化中、かつクラス記述子のストリームへの書込み後に、annotateClass
メソッドが呼び出されます。サブクラスがこのメソッドを継承して、クラスに関するほかの情報をストリームに書き込むことも可能です。この情報の読取りは、対応するObjectInputStream
サブクラスのresolveClass
メソッドを使って実行する必要があります。
ObjectOutputStream
サブクラスは、replaceObject
メソッドを実装することにより、直列化中にオブジェクトの監視および置換を実行できます。最初の置換対象オブジェクトに対してwriteObject
を呼び出す前に、enableReplaceObject
を呼び出して、置換を明示的に有効にする必要があります。オブジェクトの置換が有効にされたあと、オブジェクトをはじめて直列化する直前に、各オブジェクトに対してreplaceObject
が呼び出されます。replaceObject
メソッドは、特別に処理されるクラスであるClass
およびObjectStreamClass
のオブジェクトに対しては呼び出されません。サブクラスの実装が、元のオブジェクトではなく直列化される代替オブジェクトを返す場合があります。代替オブジェクトは、直列化可能でなければいけません。ストリームにおける元のオブジェクトへのすべての参照は、置換オブジェクトによって置き換えられます。
サブクラスはオブジェクトが置換されているときに、置換されるオブジェクトが参照を保存するすべてのフィールドと互換性が持つこと、つまり直列化復元時に補完的な置換が行われることを保証する必要があります。型がフィールドまたは配列要素の型のサブクラスでないオブジェクトは、あとでClassCastException
をスローして直列化復元を中止することになり、参照は保存されません。
enableReplaceObject
メソッドは、直列化の際にあるオブジェクトを別のオブジェクトで置換するために、ObjectOutputStream
の信頼できるサブクラスで呼び出すことができます。オブジェクトの置換は、enableReplaceObject
がtrue
値で呼び出されるまでは、無効になっています。それ以降は、false
に設定することで無効にできます。前の設定が返されます。enableReplaceObject
メソッドは、置換を要求するストリームを信頼できるかどうかを調べます。オブジェクトのprivate状態が意図せずに公開されないことを保証するために、信頼できるストリーム・サブクラスだけがreplaceObject
を使用できます。信頼されるクラスとは、Serializable置換を有効にする権限を保持する、セキュリティ保護ドメインに属するクラスのことです。
ObjectOutputStream
のサブクラスがシステム・ドメインの一部とはみなされない場合、SerializablePermission "enableSubstitution"
をセキュリティ・ポリシー・ファイルに追加する必要があります。ObjectInputStream
のサブクラスの保護ドメインに、enableReplaceObject
呼出しによる"enableSubstitution"
の権限がない場合は、AccessControlException
がスローされます。セキュリティ・モデルの詳細は、Javaセキュリティ・アーキテクチャ(JDK1.2)のドキュメントを参照してください。
writeStreamHeader
メソッドは、マジック番号とバージョンをストリームに書き込みます。この情報は、ObjectInputStream
のreadStreamHeader
メソッドを使って読み取る必要があります。ストリームの一意形式を識別するために、サブクラスがこのメソッドを実装することが必要な場合があります。
flush
メソッドは、ストリームが保持するバッファを空にして基になるストリームにフラッシュを転送するために、使用されます。基になるストリームのフラッシュを強制せずにObjectOutputStream
のバッファだけを空にするために、サブクラスからdrain
メソッドを使用できます。
プリミティブ型用のすべてのwriteメソッドは、DataOutputStream
を使って値をエンコードして、標準ストリーム形式にします。バイトがブロック・データ・レコードにバッファリングされることにより、オブジェクトのエンコーディングとの区別が可能になります。このバッファリングにより、クラスのバージョン管理が必要な場合、プリミティブ・データのスキップも可能になります。また、クラス固有のメソッドを呼び出すことなく、ストリームの構文解析を行うことも可能になります。
直列化の実装をオーバーライドするときは、ObjectOutputStream
のサブクラスは引数なしのprotected ObjectOutputStream
コンストラクタを呼び出すことをお勧めします。SerializablePermission "enableSubclassImplementation"
用の引数なしコンストラクタ内では、信頼できるクラスだけがデフォルト実装のオーバーライドを許可されるようにセキュリティ・チェックがあります。このコンストラクタは、ObjectOutputStream
にprivateなデータを割り当てず、final writeObject
メソッドがwriteObjectOverride
メソッドを呼び出してから復帰するべきことを示すフラグを設定します。ほかのすべてのObjectOutputStream
メソッドは、finalではないので、サブクラスによって直接オーバーライドできます。
2.2 ObjectOutputStream.PutFieldクラス
クラスPutField
は、あるクラスがデフォルト直列化を使わない場合に、そのクラスの直列化可能フィールドの値を設定するためのAPIを提供します。各メソッドは、指定された名前付き値をストリームに置きます。name
が、フィールドが書き込まれているクラスの直列化可能フィールドの名前と一致しない場合、または指定されたフィールドの型が、呼び出された特定のput
メソッドの2番目のパラメータの型と一致しない場合、IllegalArgumentException
がスローされます。
2.3 writeObjectメソッド
直列化可能オブジェクトの場合、writeObject
メソッドによって、クラスは独自のフィールドの直列化を制御できます。そのシグニチャを次に示します。
private void writeObject(ObjectOutputStream stream)
throws IOException;
直列化可能オブジェクトの各サブクラスは、独自のwriteObject
メソッドを定義できます。クラスがメソッドを実装しない場合は、defaultWriteObject
によって与えられるデフォルト直列化が使用されます。実装されている場合、クラスには、そのスーパー・タイプやサブタイプのフィールドではなく、独自のフィールドを書き込む責任だけがあります。
クラスのwriteObject
メソッドには、実装されている場合、クラスの状態を保存する責任があります。ObjectOutputStream
のdefaultWriteObject
メソッドまたはwriteFields
メソッドを一度(一度のみ)呼び出してからでないと、対応するreadObject
メソッドがオブジェクトの状態を復元するために必要なオプション・データを書き込むことはできません。オプション・データを書き込まない場合でも、defaultWriteObject
またはwriteFields
を一度呼び出す必要があります。オプション・データ(ある場合)の書込みの前にdefaultWriteObject
かwriteFields
が呼び出されなければ、ObjectInputStream
がそのwriteObject
メソッドを定義したクラスを解決できない場合に、インスタンス直列化復元の動作は未定義になります。
オプション・データの形式、構造体、バージョン管理の責任は、完全にクラスにあります。
2.4 writeExternalメソッド
java.io.Externalizable
を実装するオブジェクトは、writeExternal
メソッドを実装して、オブジェクトの状態全体を保存する必要があります。このオブジェクトは、そのスーパー・クラスと協調して、それらの状態を保管しなければいけません。ObjectOutput
のすべてのメソッドを、オブジェクトのプリミティブ型フィールドとオブジェクト・フィールドを保存するために使用できます。
public void writeExternal(ObjectOutput stream)
throws IOException;
JDK 1.2で、Externalizableデータを書き込むための新しいデフォルト形式が導入されました。新しい形式は、プリミティブ・データがwriteExternal
メソッドによってブロック・データ・モードで書き込まれるように指定します。さらに、writeExternal
メソッドから戻ったあとに、外部オブジェクトの末尾を示すタグがストリームに付加されます。この形式変更の利点については、セクション3.6「readExternalメソッド」を参照してください。この変更により生じる互換性の問題の詳細は、セクション2.6「useProtocolVersionメソッド」を参照してください。
2.5 writeReplaceメソッド
SerializableおよびExternalizableクラスの場合には、writeReplace
メソッドは、オブジェクトが書き込まれる前に、オブジェクトのクラスがストリーム内で独自の置換を指定することを許可します。writeReplace
メソッドを実装することにより、クラスは、直列化されている独自のインスタンスの型およびインスタンスを直接制御できます。
このメソッドは、次のように定義します。
ANY-ACCESS-MODIFIER Object writeReplace()
throws ObjectStreamException;
ObjectOutputStream
がストリームにオブジェクトを書き込む準備をしているとき、writeReplace
メソッドが呼び出されます。ObjectOutputStream
は、クラスがwriteReplace
メソッドを定義しているかどうかをチェックします。このメソッドが定義されている場合は、オブジェクトがストリーム内で置換を指定することを許可するためにwriteReplace
メソッドが呼び出されます。返されるオブジェクトは、渡されるオブジェクトと同じ型であるか、または読み込まれて解釈されたオブジェクトが、オブジェクトへのすべての参照と互換性を持つ型のオブジェクトであるべきです。そうでない場合、型の不一致が検出されたときに、ClassCastException
が発生します。
2.6 useProtocolVersionメソッド
下位互換性がなかったストリーム・プロトコルの変更によって、現在の仮想マシンが以前のリリースが読取り可能な直列化ストリームを書き込めるメカニズムが追加されました。ただし、下位互換性のあるプロトコルを使う場合は、新しいストリーム形式では修正されている問題が発生します。
ストリーム・プロトコル・バージョンについては、セクション6.3「ストリーム・プロトコル・バージョン」を参照してください。
3 - オブジェクト入力クラス
3.1 ObjectInputStreamクラス
追加ステップが追加されました
クラスObjectInputStream
は、オブジェクト直列化復元を実装します。このクラスは、すでに直列化復元されたオブジェクトのセットなど、ストリームの状態を保持します。このクラスのメソッドを使えば、ObjectOutputStream
によって書き込まれたストリームから、プリミティブ型やオブジェクトを読み込むことができます。このクラスは、それが参照するオブジェクトの、ストリームからの復元を管理します。
package java.io;
public class ObjectInputStream
extends InputStream
implements ObjectInput, ObjectStreamConstants
{
public ObjectInputStream(InputStream in)
throws StreamCorruptedException, IOException;
public final Object readObject()
throws OptionalDataException, ClassNotFoundException,
IOException;
public Object readUnshared()
throws OptionalDataException, ClassNotFoundException,
IOException;
public void defaultReadObject()
throws IOException, ClassNotFoundException,
NotActiveException;
public GetField readFields()
throws IOException;
public synchronized void registerValidation(
ObjectInputValidation obj, int prio)
throws NotActiveException, InvalidObjectException;
protected ObjectStreamClass readClassDescriptor()
throws IOException, ClassNotFoundException;
protected Class resolveClass(ObjectStreamClass v)
throws IOException, ClassNotFoundException;
protected Object resolveObject(Object obj)
throws IOException;
protected boolean enableResolveObject(boolean enable)
throws SecurityException;
protected void readStreamHeader()
throws IOException, StreamCorruptedException;
public int read() throws IOException;
public int read(byte[] data, int offset, int length)
throws IOException
public int available() throws IOException;
public void close() throws IOException;
public boolean readBoolean() throws IOException;
public byte readByte() throws IOException;
public int readUnsignedByte() throws IOException;
public short readShort() throws IOException;
public int readUnsignedShort() throws IOException;
public char readChar() throws IOException;
public int readInt() throws IOException;
public long readLong() throws IOException;
public float readFloat() throws IOException;
public double readDouble() throws IOException;
public void readFully(byte[] data) throws IOException;
public void readFully(byte[] data, int offset, int size)
throws IOException;
public int skipBytes(int len) throws IOException;
public String readLine() throws IOException;
public String readUTF() throws IOException;
// Class to provide access to serializable fields.
static abstract public class GetField
{
public ObjectStreamClass getObjectStreamClass();
public boolean defaulted(String name)
throws IOException, IllegalArgumentException;
public char get(String name, char default)
throws IOException, IllegalArgumentException;
public boolean get(String name, boolean default)
throws IOException, IllegalArgumentException;
public byte get(String name, byte default)
throws IOException, IllegalArgumentException;
public short get(String name, short default)
throws IOException, IllegalArgumentException;
public int get(String name, int default)
throws IOException, IllegalArgumentException;
public long get(String name, long default)
throws IOException, IllegalArgumentException;
public float get(String name, float default)
throws IOException, IllegalArgumentException;
public double get(String name, double default)
throws IOException, IllegalArgumentException;
public Object get(String name, Object default)
throws IOException, IllegalArgumentException;
}
protected ObjectInputStream()
throws StreamCorruptedException, IOException;
protected readObjectOverride()
throws OptionalDataException, ClassNotFoundException,
IOException;
}
単一引数のObjectInputStream
コンストラクタにはInputStream
が必要です。このコンストラクタは、readStreamHeader
を呼び出して、対応するObjectOutputStream.writeStreamHeader
メソッドによって書き込まれたヘッダーとバージョンを読み込み、検査します。セキュリティ・マネージャがインストールされている場合、このコンストラクタは、readFields
またはreadUnshared
メソッド(あるいはその両方)をオーバーライドするサブクラスのコンストラクタによって直接または間接に呼び出されたときに、"enableSubclassImplementation"
SerializablePermission
をチェックします。
ノート: ObjectInputStream
コンストラクタは、直列化ストリーム・ヘッダーの読取りが完了するまでブロックされます。ObjectInputStream
が構築されるのを待機するコードは、そのストリームの対応するObjectOutputStream
を事前に作成していない場合、デッドロックします。ObjectInputStream
コンストラクタはヘッダーがストリームに書き込まれるまでブロックされ、ヘッダーはObjectOutputStream
コンストラクタが実行されるまでストリームに書き込まれないためです。この問題は、ObjectInputStream
の前にObjectOutputStream
を作成するか、またはObjectInputStream
構築の完了とObjectOutputStream
の作成との間のタイミング依存関係を解除することによって解決できます。
ストリームからオブジェクトを直列化復元するには、readObject
メソッドを使用します。このメソッドは、オブジェクトを再構築するためにストリームから読み取ります。
ObjectInputStream
サブクラスが実装をオーバーライドしている場合は、readObjectOverride
メソッドを呼び出し、戻します。実装し直す方法については、このセクションの最後で説明します。ブロック・データ・レコードがストリーム内で検出された場合は、使用可能なバイト数で
BlockDataException
をスローします。ストリーム内のオブジェクトがnullであれば、nullを返します。
ストリーム内のオブジェクトが前のオブジェクトへのハンドルであれば、そのオブジェクトを返します。
ストリーム内のオブジェクトが
Class
であれば、そのObjectStreamClass
記述子を読み取り、それとそのハンドルを既知のオブジェクト・セットに追加し、対応するClass
オブジェクトを返します。ストリーム内のオブジェクトが
ObjectStreamClass
である場合、セクション4.3「直列化された形式」で説明されている形式に応じて、そのデータ内で読み取ります。そのオブジェクトとそのハンドルを、既知のオブジェクト・セットに追加します。Version 1.3以降のJava 2 SDK, Standard Editionでは、readClassDescriptor
メソッドがObjectStreamClass
(ダイナミック・プロキシ・クラス以外のクラスを表す場合。ストリーム・データで示される)を読み取るために呼び出されます。クラス記述子が動的プロキシ・クラスを表す場合は、記述子のローカル・クラスを取得するためにストリーム上でresolveProxyClass
メソッドを呼び出します。そうでない場合は、ローカル・クラスを取得するためにストリーム上でresolveClass
メソッドを呼び出します。クラスが解決できない場合は、ClassNotFoundExceptionをスローします。結果として得られるObjectStreamClass
オブジェクトを返します。ストリーム内のオブジェクトが
String
の場合、その長さ情報とmodified UTF-8でエンコードされた文字列の内容を読み取ります。詳細は、セクション6.2「ストリーム要素」を参照してください。String
とそのハンドルを既知のオブジェクトのセットに追加して、ステップ123に進みます。ストリーム内のオブジェクトが配列であれば、その
ObjectStreamClass
と配列の長さを読み取ります。配列を割り当て、それとそのハンドルを既知のオブジェクトのセットに追加します。各要素をその型に適したメソッドを使って読み取り、配列に代入します。ステップ123に進みます。ストリーム内のオブジェクトがenum定数であれば、その
ObjectStreamClass
とenum定数名を読み取ります。ObjectStreamClass
がenum型ではないクラスを表す場合は、InvalidClassException
がスローされます。受け取ったObjectStreamClass
にバインドされたenum型と受け取った名前と一緒に引数として渡すjava.lang.Enum.valueOf
メソッドを呼び出すことで、enum定数への参照を取得します。valueOf
メソッドがIllegalArgumentException
をスローした場合、IllegalArgumentException
を原因としてInvalidObjectException
がスローされます。enum定数とそのハンドルを既知のオブジェクトのセットに追加して、ステップ123に進みます。その他のオブジェクトの場合は、そのオブジェクトの
ObjectStreamClass
がストリームから読み取られます。そのObjectStreamClass
のローカル・クラスが取り出されます。このクラスは、直列化可能または外部化可能である必要があります。また、enum型以外である必要があります。クラスがこの条件を満たさない場合は、InvalidClassException
がスローされます。
クラスがレコード・クラスである場合。初期値
null
を持つハンドルが一連の既知のオブジェクトに追加されます。レコード・オブジェクトは、canonicalコンストラクタの呼出しによって構築されます。レコード・クラス
R
の正規コンストラクタは、最初に、R::getRecordComponents
によって返されるレコードRのコンポーネントの数、順序および宣言された型からメソッド記述子を構築してから、この記述子と一致するRの宣言されたコンストラクタを特定することにより、見つけることができます。canonicalコンストラクタが見つからない場合、InvalidClassException
がスローされます。コンテンツは、次のように復元されます。
ストリームからすべてのフィールド値を読み取って復元します。このストリーム・フィールドを、レコード・コンポーネントを初期化するために使用される適切なコンストラクタ・パラメータのこのフィールドと照合します。この照合は、こストリーム・フィールドの名前とレコード・コンポーネントの名前の同等性に基づいています。これらの名前は同等である必要があります。つまり、
Class::getRecordComponents
によって返されるレコード・コンポーネントごとに、最初に、コンポーネントの名前n
を確認してから、名前がn
と等しいストリーム・フィールドのストリーム値を見つけます。一致したフィールドのストリーム値の具体的な型は、一致したレコード・コンポーネントの具体的な型に割当て可能(Class::isAssignableFrom
)である必要があります。そうでない場合、ClassCastException
がスローされます。一致したものはすべて型がチェックされます。一致しないストリーム・フィールドは実質的に破棄されます。- 一致したストリーム・フィールド値を使用してレコードの正規コンストラクタを呼び出します。ストリーム・フィールド値は、コンストラクタ・パラメータの対応するレコード・コンポーネントの位置に渡されます。一致しないコンポーネントは、渡された型に適したデフォルト値を持ちます。コンポーネントの呼出しによって例外がスローされた場合、この例外を原因として
InvalidObjectException
がスローされます。そうでない場合、新しく作成されたレコード・オブジェクトが既知のオブジェクトのハンドルに割り当てられます。ステップ13に進みます。
112.クラスのインスタンスが割り当てられます。インスタンスとそのハンドルが既知のオブジェクトのセットに追加されます。内容が適切に復元されます。
a. For serializable objects, the no-arg constructor for the first
non-serializable supertype is run. For serializable classes, the fields
are initialized to the default value appropriate for its type. Then the
fields of each class are restored by calling class-specific
`readObject` methods, or if these are not defined, by calling the
`defaultReadObject` method. Note that field initializers and
constructors are not executed for serializable classes during
deserialization. In the normal case, the version of the class that
wrote the stream will be the same as the class reading the stream. In
this case, all of the supertypes of the object in the stream will match
the supertypes in the currently-loaded class. If the version of the
class that wrote the stream had different supertypes than the loaded
class, the `ObjectInputStream` must be more careful about restoring or
initializing the state of the differing classes. It must step through
the classes, matching the available data in the stream with the classes
of the object being restored. Data for classes that occur in the
stream, but do not occur in the object, is discarded. For classes that
occur in the object, but not in the stream, the class fields are set to
default values by default serialization.
b. For externalizable objects, the no-arg constructor for the class is run
and then the `readExternal` method is called to restore the contents of
the object.
123.オブジェクトのクラスまたはObjectInputStream
:のサブクラス(あるいはその両方)による潜在的な置換を処理します。
a. If the class of the object is not an enum type and defines the
appropriate `readResolve` method, the method is called to allow the
object to replace itself.
b. Then if previously enabled by `enableResolveObject,` the
`resolveObject` method is called to allow subclasses of the stream to
examine and replace the object. If the previous step did replace the
original object, the `resolveObject` method is called with the
replacement object. If a replacement took place, the table of known
objects is updated so the replacement object is associated with the
handle. The replacement object is then returned from `readObject`.
プリミティブ型を読む取るためのすべてのメソッドは、ストリームのブロック・データ・レコードからのバイトのみを消費します。ストリーム内の次のアイテムがオブジェクトのときにプリミティブ・データの読込みが行われると、読込みメソッドは-1かEOFException
のうちで適切な方を返します。プリミティブ型の値は、DataInputStream
によってブロック・データ・レコードから読み込まれます。
スローされる例外は、そのトラバーサル中のエラー、または基になるストリームで起きる例外を反映します。例外がスローされた場合、基になるストリームは不明で使用不能な状態になります。
ストリームでリセット・トークンが起こると、ストリームのすべての状態は破棄されます。既知のオブジェクトのセットはクリアされます。
ストリームで例外トークンが起こると、例外が読み込まれ、終了させた例外を引数として新しいWriteAbortedException
がスローされます。ストリーム・コンテキストは前に述べたようにリセットされます。
ストリームから非共有オブジェクトを読み込むには、readUnshared
メソッドを使用します。このメソッドはreadObject
と同じですが、後続のreadObject
およびreadUnshared
の呼出しが、元のreadUnshared
の呼出しによって返される直列化復元インスタンスへの追加参照を返すことができない点が異なります。具体的には、次のようになります。
readUnshared
を呼び出して逆参照(以前にストリームに書き込まれたオブジェクトのストリーム表現)を直列化復元しようとすると、ObjectStreamException
がスローされます。readUnshared
が正常に復帰したあとで、readUnshared
が直列化復元したストリーム・ハンドルへの逆参照を直列化復元しようとすると、ObjectStreamException
がスローされます。
readUnshared
でオブジェクトを直列化復元すると、返されるオブジェクトに関連付けられたストリーム・ハンドルが無効になります。ただし、これ自体がreadUnshared
から返される参照が一意であることを保証するとはかぎりません。直列化復元されたオブジェクトで定義されているreadResolve
メソッドが、他の関係者が見ることができるオブジェクトを返したり、readUnshared
がストリームの内部または外部から取得できるClass
オブジェクトまたはenum定数を返したりする可能性があります。直列化復元されたオブジェクトがreadResolve
メソッドを定義し、このメソッドの呼出しが配列を返す場合、readUnshared
はその配列のシャロー・クローンを返します。これにより、基になるデータ・ストリームが操作された場合でも、返される配列オブジェクトが一意であり、ObjectInputStream
上のreadObject
またはreadUnshared
呼出しから2回取得できないことが保証されます。
ストリームからフィールドおよびオブジェクトを読み込むには、defaultReadObject
メソッドを使用します。このメソッドは、ストリーム内のクラス記述子を使って、名前と型による正規順序でストリームからフィールドを読み込みます。値は、現在のクラス内の名前で一致するフィールドに代入されます。バージョン管理メカニズムの詳細は、セクション5.5「互換性のあるJavaの型展開」を参照してください。ストリーム内にないオブジェクトのフィールドは、そのデフォルト値に設定されます。ストリームにあるがオブジェクトにない値は、破棄されます。このような状態は主に、前のバージョンにはなかったフィールドを、クラスのあとのバージョンに追加で書き込んだ場合に起こります。このメソッドは、クラスのフィールドを復元している間にreadObject
メソッドからのみ呼び出すことができます。それ以外のときに呼び出すと、NotActiveException
がスローされます。
readFields
メソッドは、ストリームから直列化可能フィールドの値を読み取り、GetField
クラスでその値を取得できるようにします。readFields
メソッドは、直列化可能クラスのreadObject
メソッド内からしか呼び出すことができません。このメソッドは、1回より多く、またはdefaultReadObject
が呼び出されている場合に、呼び出せません。GetFields
オブジェクトは、現在のオブジェクトのObjectStreamClass
を使って、このクラスのために取得できるフィールドを確認します。readFields
によって返されるGetFields
オブジェクトは、クラスreadObject
メソッドへのこの呼出しの間だけ有効です。フィールドは、任意の順序で取得できます。追加データの読込みは、readFields
が呼び出されたあとに、ストリームから直接読み込む場合だけ可能です。
registerValidation
メソッドは、グラフ全体が復元されたけれどもオブジェクトがreadObject
の元の呼出し側に返される前のときに、コールバックを要求するために呼び出すことができます。検証コールバックの順序は、優先順位で制御できます。高い値のコールバックは、低い値のものより前に呼び出されます。検証されるオブジェクトは、ObjectInputValidation
インタフェースをサポートし、validateObject
メソッドを実装しなければいけません。クラスのreadObject
メソッド呼出し中に検証を登録することのみが正しいです。そうでない場合は、NotActiveException
がスローされます。registerValidation
に指定されたコールバック・オブジェクトがnullの場合、InvalidObjectException
がスローされます。
Java SDK, Standard Edition, v1.3から、すべてのObjectStreamClass
オブジェクトを読み取るためにreadClassDescriptor
メソッドが使用されています。直列化ストリーム内でObjectInputStream
が次の項目としてクラス記述子を期待する場合は、readClassDescriptor
が呼び出されます。非標準の形式で(writeClassDescriptor
メソッドをオーバーライドしたObjectOutputStream
のサブクラスによって)記述されたクラス記述子内で読み取るために、ObjectInputStream
のサブクラスがこのメソッドをオーバーライドできます。デフォルトでは、このメソッドは、セクション6.4「ストリーム形式の文法」で説明している形式に従ってクラス記述子を読み取ります。
resolveClass
メソッドは、クラスが直列化復元されている間、かつクラス記述子が読み込まれたあとで呼び出されます。サブクラスは、ObjectOutputStream
の対応するサブクラスで記述された、クラスに関するほかの情報を読み取るために、このメソッドを拡張できます。メソッドは、指定された名前とserialVersionUID
を持つクラスを見つけて返す必要があります。デフォルト実装は、クラス・ローダーを持つreadObject
のもっとも近い呼び出し側のクラス・ローダーを呼び出すことによって、クラスを見つけます。クラスが見つからない場合は、ClassNotFoundException
がスローされるはずです。JDK 1.1.6より前では、resolveClass
メソッドは、ストリーム内のクラス名と同じ完全修飾クラス名を返す必要がありました。JDK 1.1.6以降のバージョンでは、リリースをまたがったパッケージ名変更に対応するために、method
resolveClass
に必要なのは、同じ基底クラス名とSerialVersionUID
を持つクラスを返すことだけです。
resolveObject
メソッドは、直列化復元の際に、あるオブジェクトを監視したり別のオブジェクトに置換したりするために、信頼できるサブクラスによって使用されます。最初のオブジェクトを解決するには、readObject
を呼び出す前にenableResolveObject
を呼び出すことでオブジェクトの解決を明示的に有効にする必要があります。有効になったresolveObject
は、各直列化可能オブジェクトがreadObject
から最初に返される直前に、一度だけ呼び出されます。resolveObject
メソッドは、特別に処理されるクラスClass
、ObjectStreamClass
、String
、および配列のオブジェクトの場合は呼び出されません。サブクラスのresolveObject
の実装は、オリジナルの代わりに代入または返される置換オブジェクトを返す場合があります。返されるオブジェクトは、元のオブジェクトのすべての参照と一貫性を持ちそれらに代入可能な型である必要があり、そうでない場合はClassCastException
がスローされます。すべての代入は型チェックされます。ストリーム内の、元のオブジェクトへのすべての参照は、置換オブジェクトへの参照によって置き換えられます。
enableResolveObject
メソッドは、直列化復元の際に、あるオブジェクトを監視したり別のオブジェクトに置換したりするために、ObjectOutputStream
の信頼できるサブクラスによって呼び出されます。オブジェクトの置換は、enableResolveObject
がtrue
値で呼び出されるまで無効になっています。それ以降は、false
に設定することで無効にできます。前の設定が返されます。enableResolveObject
メソッドは、直列化の際にストリームが置換を要求する権限を持つかどうかを検査します。オブジェクトのprivate状態が意図せずに公開されることがないように、信頼できるストリームだけがresolveObject
を使用できます。信頼できるクラスとは、クラス・ローダーがnullに等しいか、置換を有効にする権限を提供するセキュリティ保護ドメインに属するクラスのことです。
ObjectInputStream
のサブクラスがシステム・ドメインの一部とみなされない場合は、enableResolveObject
を呼び出す権限をObjectInputStream
のサブクラスに提供する行をセキュリティ・ポリシー・ファイルに追加する必要があります。追加するSerializablePermission
は、"enableSubstitution"
です。ObjectStreamClass
のサブクラスの保護ドメインがenableResolveObject
を呼び出して"enableSubstitution"
する権限を持たない場合は、AccessControlException
がスローされます。セキュリティ・モデルの詳細は、Javaセキュリティ・アーキテクチャ(JDK 1.2)のドキュメントを参照してください。
readStreamHeader
メソッドは、ストリームのマジック番号とバージョンを読み込み、検査します。それらが一致しない場合、StreamCorruptedMismatch
がスローされます。
直列化復元の実装をオーバーライドするには、ObjectInputStream
のサブクラスがprotected引数なしObjectInputStream
コンストラクタを呼び出す必要があります。SerializablePermission "enableSubclassImplementation"
用の引数なしコンストラクタ内では、信頼できるクラスだけがデフォルト実装のオーバーライドを許可されるようにセキュリティ・チェックがあります。このコンストラクタは、ObjectInputStream
にprivateデータを割り当てず、final readObject
メソッドがreadObjectOverride
メソッドを呼び出して復帰すべきことを示すフラグを設定します。ほかのすべてのObjectInputStream
メソッドは、finalではないので、サブクラスによって直接オーバーライドできます。
3.2 ObjectInputStream.GetFieldクラス
ObjectInputStream.GetField
クラスは、直列化可能フィールドの値を取得するためのAPIを提供します。ストリームのプロトコルは、defaultReadObject
によって使用されるものと同じです。readFields
を使用して直列化可能フィールドにアクセスしても、ストリームの形式は変更されません。それらの値にアクセスするための代替APIを提供するのみです(指定された各直列化可能フィールドに対応するtransientでもstaticでもないフィールドを持つことをクラスに要求しません)。直列化可能フィールドとは、serialPersistentFields
を使用して宣言されたフィールド(宣言されていない場合は、オブジェクトのtransientでもstaticでもないフィールド)のことです。ストリームが読み取られるときに利用可能な直列化可能フィールドは、オブジェクトが直列化されたときにストリームに書き込まれたフィールドです。ストリームを書き込んだクラスが異なるバージョンの場合は、すべてのフィールドが現在のクラスの直列化可能フィールドに対応するわけではありません。利用可能なフィールドは、GetField
オブジェクトのObjectStreamClass
から取得できます。
getObjectStreamClass
メソッドは、ストリーム内のクラスを表すObjectStreamClass
オブジェクトを返します。これには、直列化可能フィールドのリストが含まれています。
defaulted
メソッドは、ストリーム内にフィールドが存在しない場合は、trueを返します。要求されたフィールドが現在のクラスの直列化可能フィールドでない場合は、IllegalArgumentException
がスローされます。
各get
メソッドは、指定された直列化可能フィールドをストリームから返します。基になるストリームが例外をスローした場合は、入出力例外がスローされます。名前または型が現在のクラスの直列化可能フィールドの名前および型に一致しない場合は、IllegalArgumentException
がスローされます。フィールドの明示的な値がストリームに含まれていない場合は、デフォルト値が返されます。
3.3 ObjectInputValidationインタフェース
このインタフェースを使用することで、オブジェクトの完全なグラフ(オブジェクトが構成する完全グラフ)が直列化復元されたときに、オブジェクトを呼び出すことができます。オブジェクトを有効にできない場合は、ObjectInvalidException
をスローするはずです。validateObject
呼出し中に例外が発生すると、検証処理が終了し、InvalidObjectException
がスローされます。
package java.io;
public interface ObjectInputValidation
{
public void validateObject()
throws InvalidObjectException;
}
3.4 readObjectメソッド
直列化可能オブジェクトの場合、クラスはreadObject
メソッドによって独自のフィールドの直列化復元を制御できます。そのシグニチャを次に示します。
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
直列化可能オブジェクトの各サブクラスは、独自のreadObject
メソッドを定義できます。クラスがメソッドを実装しない場合は、defaultReadObject
によって提供されるデフォルト直列化が使用されます。実装されたクラスは、そのスーパー・タイプまたはサブタイプのフィールドではなく、独自のフィールドの復元だけに責任を持ちます。
クラスのreadObject
メソッドは(実装されている場合)、そのクラスの状態を復元する責任を持ちます。transientかどうかstaticかどうかには関係なく、オブジェクトの各フィールドの値は、フィールド型のデフォルト値に設定されます。ObjectInputStream
のdefaultReadObject
またはreadFields
メソッドは、対応するwriteObject
メソッドによって書き出されたオプション・データを読み取る前に、一度のみ呼び出す必要があります。オプション・データを読み取らない場合でも、defaultReadObject
またはreadFields
を一度呼び出す必要があります。クラスのreadObject
メソッドが、このクラスのストリームのオプション部分に存在するデータより多くのデータを読み取ろうとすると、ストリームはバイト単位読取りの場合は-1
を返し、プリミティブ・データ読取り(readInt
、readFloat
など)の場合はEOFException
をスローし、オブジェクト読取りの場合はeof
フィールドがtrue
に設定されたOptionalDataException
をスローします。
オプション・データの形式、構造体、バージョン管理の責任は、完全にクラスにあります。オプション・データのフォーマットおよび構造体を文書化する場合は、readObject
メソッドのjavadocコメント内で@serialData
javadocタグを使うようにしてください。
復元するクラスが読み取るストリーム内に存在しない場合、readObjectNoData
メソッドが(定義されている場合)呼び出され(readObject
の代わりに)、そうでない場合はそのフィールドは適切なデフォルト値に初期化されます。詳細は、セクション3.5「readObjectNoDataメソッド」を参照してください。
ObjectInputStream
からのオブジェクトの読込みは、新しいオブジェクトの作成に似ています。新しいオブジェクトのコンストラクタがスーパー・クラスからサブクラスの順番で呼び出されるのと同様に、ストリームから読み込まれるオブジェクトは、スーパー・クラスからサブクラスに直列化復元されます。直列化復元中は、Serializable
サブクラスごとに、コンストラクタの代わりにreadObject
またはreadObjectNoData
メソッドが呼び出されます。
コンストラクタとreadObject
メソッドでもう1つ似ている点は、どちらも、完全に構築されていないオブジェクトでメソッドを呼び出す機会を提供することです。オブジェクトの構築中に呼び出されるオーバーライド可能なメソッド(private、static、finalのどれでもないもの)は、サブクラスによって潜在的にオーバーライドされている場合があります。オブジェクトの構築段階に呼び出されるメソッドは、コンストラクタまたはreadObject
/readObjectNoData
メソッドによって現在初期化されている型ではなく、オブジェクトの実際の型によって解決されます。したがって、readObject
またはreadObjectNoData
メソッド内からオーバーライド可能なメソッドを呼び出すと、スーパー・クラスが完全に初期化される前にサブクラスが意図せずに呼び出される可能性があります。
3.5 readObjectNoDataメソッド
直列化可能オブジェクトの場合、サブクラス・インスタンスが直列化復元され、直列化ストリームがクラスを直列化復元されたオブジェクトのスーパー・クラスとしてリストしない場合、そのクラスはreadObjectNoData
メソッドを使用して独自のフィールドの初期化を制御できます。これは、受取り側が送り側とは異なるバージョンの直列化復元されたインスタンスのクラスを使用し、受取り側のバージョンが送り側のバージョンによって継承されないクラスを継承する場合に発生する可能性があります。また、直列化ストリームが改変された場合にも発生することがあります。したがって、readObjectNoData
は、「悪意のある」または不完全なソース・ストリームであっても、直列化復元されたオブジェクトを正しく初期化するのに役立ちます。
private void readObjectNoData() throws ObjectStreamException;
直列化可能クラスごとに、独自のreadObjectNoData
メソッドを定義できます。直列化可能クラスがreadObjectNoData
メソッドを定義しない場合、前述の状況では、クラスのフィールドは(「Java Language Specification」に記載された)デフォルト値に初期化されます。この動作は、readObjectNoData
メソッドのサポートが導入されたJava 2 SDK, Standard Edition Version 1.4より前のObjectInputStream
の動作と一致します。直列化可能クラスがreadObjectNoData
メソッドを定義していて、前述の状況が発生した場合は、直列化復元中のその時点でreadObjectNoData
が呼び出されます(そのクラスがストリームによって、直列化復元するインスタンスのスーパー・クラスとしてリストされた場合は、クラス定義のreadObject
メソッドが呼び出される)。
3.6 readExternalメソッド
java.io.Externalizable
を実装するオブジェクトが、オブジェクトの状態全体を復元するには、readExternal
メソッドを実装する必要があります。スーパー・クラスの状態を復元するには、スーパー・クラスと連携する必要があります。ObjectInput
のすべてのメソッドを、オブジェクトのプリミティブ型フィールドとオブジェクト・フィールドを復元するために使用できます。
public void readExternal(ObjectInput stream)
throws IOException;
ノート: readExternal
メソッドはpublicであるため、クライアントがストリームの既存オブジェクトを上書きしてしまう危険があります。適切なときにだけ呼び出されるように、クラスに独自のチェックを追加することもできます。
Externalizable
オブジェクトの問題を修正するために、JDK 1.2で新しいストリーム・プロトコル・バージョンが導入されました。Externalizable
オブジェクトの以前の定義では、ストリームからExternalizable
オブジェクトを適切に読み込めるようにするために、ローカル仮想マシンがreadExternal
メソッドを探す必要がありました。新しいフォーマットによって十分な情報がストリーム・プロトコルに追加されるので、ローカルreadExternal
メソッドが使えない場合に直列化はExternalizable
オブジェクトをスキップできます。クラス展開規則により、ローカル・クラスを使用するオブジェクトのマッピングがない場合は、直列化は入力ストリーム内のExternalizable
オブジェクトをスキップできる必要があります。
新しいExternalizable
ストリーム形式には、利用できるExternalデータより多くのデータの読取りをObjectInputStream
が検出し、readExternal
メソッドが消費されていないデータをスキップできるという利点もあります。Externalデータの終わりを越えた読取りに対するObjectInputStream
の動作は、クラス定義のreadObject
メソッドがオプション・データの終わりを越えて読み取ろうとしたときの動作と同じです。バイト単位読取りは-1
を返し、プリミティブ読取りはEOFException
をスローし、オブジェクト読取りはeof
フィールドがtrue
に設定されたOptionalDataException
をスローします。
フォーマット変更のため、JDK 1.1.6以前のリリースは新しいフォーマットを読み取れません。JDK 1.1.6以前がPROTOCOL_VERSION_2
で書き込まれたストリームからExternalizable
オブジェクトを読み取ろうとすると、StreamCorruptedException
がスローされます。互換性の問題については、セクション6.3「ストリーム・プロトコル・バージョン」を参照してください。
3.7 readResolveメソッド
SerializableクラスとExternalizableクラスの場合、クラスはreadResolve
メソッドを使うことによって、呼出し側に返される前に、ストリームから読み込んだオブジェクトを置換または解決できます。readResolve
メソッドを実装することによって、クラスは、クラス自体の直列化復元されているインスタンスの型およびインスタンスを直接制御できます。このメソッドは、次のように定義します。
ANY-ACCESS-MODIFIER Object readResolve()
throws ObjectStreamException;
readResolve
メソッドは、ObjectInputStream
がストリームからオブジェクトを読み込み、呼出し側に返す準備をしているときに呼び出されます。ObjectInputStream
は、オブジェクトのクラスがreadResolve
メソッドを定義しているかどうかを確認します。メソッドが定義されている場合は、ストリーム内のオブジェクトが、返されるオブジェクトを指定できるように、readResolve
メソッドが呼び出されます。返されるオブジェクトは、すべての使用場面で互換性がある型でなければいけません。互換性がない場合は、型の不一致が発見された時点でClassCastException
がスローされます。
たとえば、Symbol
クラスを作成して、シンボル・バインディングごとにインスタンスが仮想マシン内に1つだけ存在していたとします。readResolve
メソッドは、そのシンボルがすでに定義されているかどうかを判断し、アイデンティティ制約を保持するために既存の等価Symbol
オブジェクトを置換するために実装されます。こうすることで、Symbol
オブジェクトの一意性を直列化をまたがって保持できます。
ノート: readResolve
メソッドはオブジェクトが完全に構築されるまでオブジェクトで呼び出されないため、オブジェクト・グラフ内でのこのオブジェクトへの参照は、readResolve
によって指名された新しいオブジェクトに更新されません。しかし、writeReplace
メソッドによるオブジェクトの直列化の間に、置換オブジェクトのオブジェクト・グラフ内での元のオブジェクトへのすべての参照は、置換オブジェクトへの参照に置き換えられます。したがって、直列化されているオブジェクトが置換オブジェクト(そのオブジェクト・グラフが元のオブジェクトへの参照を持つ)を指名している場合は、直列化復元によって間違ったオブジェクト・グラフになります。さらに、読み取っているオブジェクト(writeReplace
によって指名された)と元のオブジェクトの参照型が互換でない場合、オブジェクト・グラフの構築はClassCastException
をスローします。
4 - クラス記述子
4.1 ObjectStreamClassクラス
ObjectStreamClass
は、直列化ストリームに保存されるクラスの情報を提供します。この記述子は、クラスの完全修飾名とその直列化バージョンUIDを提供します。SerialVersionUID
は、このクラスがストリームを書き込んだりストリームから読み込んだりできる、一意のオリジナル・クラス・バージョンを特定します。
package java.io;
public class ObjectStreamClass
{
public static ObjectStreamClass lookup(Class cl);
public static ObjectStreamClass lookupAny(Class cl);
public String getName();
public Class forClass();
public ObjectStreamField[] getFields();
public long getSerialVersionUID();
public String toString();
}
lookup
メソッドは、仮想マシン内の指定されたクラスのObjectStreamClass
記述子を返します。クラスにserialVersionUID
が定義されていれば、それがクラスから取り出されます。serialVersionUID
がクラスによって定義されていなければ、仮想マシン内のクラスの定義から計算されます。指定されたクラスがSerializableでもExternalizableでもない場合、nullが返されます。
lookupAny
メソッドの動作はlookup
メソッドの動作と似ていますが、Serializable
を実装しているかどうかに関係なくクラスの記述子を返す点が異なります。Serializable
を実装しないクラスのserialVersionUID
は0Lです。
getName
メソッドが返すクラス名の形式は、Class.getName
メソッドが使用する形式と同じになります。
forClass
メソッドは、ローカル仮想マシン内のClass
(ObjectInputStream.resolveClass
メソッドが検出した場合)を返します。それ以外の場合は、nullを返します。
getFields
メソッドは、このクラスの直列化可能フィールドを表すObjectStreamField
オブジェクトの配列を返します。
getSerialVersionUID
メソッドは、このクラスのserialVersionUID
を返します。セクション4.6「ストリーム固有識別子」を参照してください。このクラスによって指定されていない場合は、米国国立標準技術研究所によって定義されているSecure Hash Algorithm (SHA)を使って、クラスの名前、インタフェース、メソッド、フィールドから計算されたハッシュ値が返されます。
toString
メソッドは、クラスの名前とserialVersionUID
も含め、クラス記述子の出力可能な表現を返します。
4.2 ダイナミック・プロキシ・クラス記述子
ObjectStreamClass記述子を使って、直列化ストリームに保存されているダイナミック・プロキシ・クラス(java.lang.reflect.ProxyのgetProxyClassメソッドへの呼出しを介して取得されるクラスなど)についての情報を提供することもできます。ダイナミック・プロキシ・クラス自体は直列化可能フィールドを持たず、0LのserialVersionUIDを持ちます。つまり、ダイナミック・プロキシ・クラスのClassオブジェクトがObjectStreamClassのstatic lookupメソッドに渡されると、返されるObjectStreamClassインスタンスは、次のプロパティを持ちます。
- getSerialVersionUIDメソッドを呼び出すと、0Lが返されます。
- getFieldsメソッドを呼び出すと、ゼロ長の配列が返されます。
- 任意のString引数でgetFieldメソッドを呼び出すと、nullが返されます。
4.3 直列化された形式
ObjectStreamClassインスタンスの直列化された形式は、その形式が表現するClassオブジェクトが直列化可能であるか、外部化可能であるか、またはダイナミック・プロキシ・クラスであるかによって異なります。
ダイナミック・プロキシ・クラスを表現しないObjectStreamClass
インスタンスがストリームに書き込まれるときには、クラス名とserialVersionUID
、フラグ、およびフィールド数が書き込まれます。クラスによっては、その他の情報が書き込まれることもあります。
直列化が不可能なクラスは、フィールド数が常にゼロです。
SC_SERIALIZABLE
およびSC_EXTERNALIZABLE
フラグ・ビットは設定されません。直列化可能クラスでは、
SC_SERIALIZABLE
フラグが設定され、フィールド数には直列化可能フィールドの数がカウントされ、そのあとに各直列化可能フィールドの記述子が続きます。記述子は、正規順序で書き込まれます。最初に、プリミティブ型フィールドの記述子がフィールド名でソートされて書き込まれ、次に、オブジェクト型フィールドの記述子がフィールド名でソートされて書き込まれます。名前のソートには、String.compareTo
が使われます。形式の詳細は、セクション6.4「ストリーム形式の文法」を参照してください。外部化可能クラスでは、フラグは
SC_EXTERNALIZABLE
フラグを含み、フィールド数は常にゼロです。enum型では、フラグは
SC_ENUM
フラグを含み、フィールド数は常にゼロです。
ObjectOutputStreamがダイナミック・プロキシ・クラスのObjectStreamClass記述子を直列化するときは、ダイナミック・プロキシ・クラス(Classオブジェクトをjava.lang.reflect.ProxyのisProxyClassメソッドに渡すことによって決定される)が実装するインタフェース数とインタフェースの名前を書き込みます。インタフェースは、ダイナミック・プロキシ・クラスのClassオブジェクトでgetInterfacesメソッドを呼び出すことで返される順序に従ってリストされます。
ダイナミック・プロキシ・クラスおよび非ダイナミック・プロキシ・クラスのObjectStreamClass記述子の直列化表現は、使用する型コードの種類(TC_PROXYCLASSDESC
およびTC_CLASSDESC
のどちらか)によって異なります。文法の仕様の詳細は、セクション6.4「ストリーム形式の文法」を参照してください。
4.4 ObjectStreamFieldクラス
ObjectStreamField
は、直列化可能クラスの直列化可能フィールドを表現します。クラスの直列化可能フィールドは、ObjectStreamClass
から取得できます。
特別なstatic直列化可能フィールドserialPersistentFields
は、ObjectStreamField
コンポーネントの配列であり、デフォルトの直列化可能フィールドのオーバーライドに使用されます。
package java.io;
public class ObjectStreamField implements Comparable {
public ObjectStreamField(String fieldName,
Class fieldType);
public ObjectStreamField(String fieldName,
Class fieldType,
boolean unshared);
public String getName();
public Class getType();
public String getTypeString();
public char getTypeCode();
public boolean isPrimitive();
public boolean isUnshared();
public int getOffset();
protected void setOffset(int offset);
public int compareTo(Object obj);
public String toString();
}
ObjectStreamField
オブジェクトは、クラスの直列化可能フィールドの指定、またはストリームに存在するフィールドの記述に使用されます。そのコンストラクタは、表現するフィールドを記述する引数を受け取ります。引数には、フィールドの名前を指定する文字列、フィールドの型を指定するClass
オブジェクト、およびデフォルトの直列化/直列化復元が使用中の場合に表現されるフィールドの値を非共有オブジェクトとして読み書きする必要があるかどうかを示すboolean
フラグ(2つの引数を取るコンストラクタでは暗黙的にfalse
)があります(セクション3.1「ObjectInputStreamクラス」およびセクション2.1「ObjectOutputStreamクラス」のObjectInputStream.readUnshared
およびObjectOutputStream.writeUnshared
メソッドの説明をそれぞれ参照)。
getName
メソッドは、直列化可能フィールドの名前を返します。
getType
メソッドは、フィールドの型を返します。
getTypeString
メソッドは、フィールドの型シグニチャを返します。
getTypeCode
メソッドは、フィールド型の文字エンコーディングを返します('B
'はbyte
、'C
'はchar
、'D
'はdouble
、'F
'はfloat
、'I
'はint
、'J
'はlong
、'L
'は非配列オブジェクト型、'S
'はshort
、'Z
'はboolean
、'[
'は配列)。
isPrimitive
メソッドは、フィールドがプリミティブ型の場合はtrue
を返し、それ以外の場合はfalse
を返します。
isUnshared
メソッドは、フィールドの値を非共有オブジェクトとして書き込むべき場合はtrue
を返し、それ以外の場合はfalse
を返します。
getOffset
メソッドは、フィールドを定義するクラスのインスタンス・データ内でのフィールド値のオフセットを返します。
setOffset
メソッドは、getOffset
メソッドから返されたオフセット値をObjectStreamField
サブクラスで変更できるようにします。
compareTo
メソッドは、ソートに使用するためにObjectStreamFields
を比較します。プリミティブ・フィールドは、非プリミティブ・フィールドよりも「小さい」順位にランク付けられます。等しいフィールドは、アルファベット順にランク付けられます。
toString
メソッドは、名前と型による出力可能な表現を返します。
4.5 直列化可能クラスの検査
プログラムserialverを使うと、クラスが直列化可能かどうかを判断し、そのserialVersionUID
を取得できます。
1つまたは複数のクラス名付きでコマンド行から呼び出されたserialverは、展開中のクラスにコピーするのに適した形式で各クラスのserialVersionUID
を出力します。引数が指定されていないと、このプログラムの使用方法が出力されます。
4.6 ストリーム固有識別子
レコードのSerialVersionUIDの小幅な変更
バージョン管理された各クラスは、ストリームを書き込んだりストリームから読み込んだりできる、オリジナル・クラス・バージョンを識別する必要があります。たとえば、バージョン管理されたクラスは、次のように宣言する必要があります。
private static final long serialVersionUID = 3487495895819393L;
ストリーム固有識別子は、クラス名、インタフェース・クラス名、メソッド、およびフィールドの64ビット・ハッシュです。最初のバージョンを除くクラスのすべてのバージョンで、この値を宣言する必要があります。この値は、オリジナル・クラスに宣言することもできますが、必須ではありません。互換性のあるすべてのクラスで、この値は一定です。クラスのSUIDを宣言しない場合は、値はデフォルトでそのクラスのハッシュになります。ダイナミック・プロキシ・クラスおよびenum型のserialVersionUID
は常に、値0Lになります。配列クラスは明示的なserialVersionUID
を宣言できないため、常にデフォルト計算値を持ちますが、配列クラスに関してはserialVersionUID
値の一致要件は適用されません。レコード・クラスは、0L
のデフォルト値serialVersionUID
を持ちますが、明示的なserialVersionUID
を宣言できます。レコード・クラスについてのserialVersionUID
値の照合に関する要件は免除されます。
ノート: すべての直列化可能クラスは、serialVersionUID
値を明示的に宣言することをお薦めします。これは、デフォルトのserialVersionUID
計算は、コンパイラ実装により異なることがあるクラス詳細によって大きく変わり、そのため直列化復元中に予期しないserialVersionUID
競合が発生して直列化復元が失敗する可能性があるためです。
Externalizable
クラスの初期バージョンでは、将来的に拡張可能なストリーム・データ形式を出力する必要があります。readExternal
メソッドの初期バージョンは、writeExternal
メソッドの将来のすべてのバージョンの出力形式を読取り可能でなければいけません。
serialVersionUID
は、クラス定義を反映したバイト・ストリームのシグネチャを使用して計算されます。ストリームのシグネチャの計算には、米国国立標準技術研究所(NIST)のSecure Hash Algorithm (SHA-1)が使用されます。64ビット・ハッシュには、最初の2つの32ビット数が使用されます。プリミティブ・データ型からバイト・シーケンスへの変換には、java.lang.DataOutputStream
が使用されます。ストリームに入力される値は、クラスのJava仮想マシン(VM)仕様によって定義されます。クラス修飾子にはACC_PUBLIC
、ACC_FINAL
、ACC_INTERFACE
、ACC_ABSTRACT
フラグを含めることができます。その他のフラグは無視され、serialVersionUID
の計算に影響しません。同様に、フィールド修飾子では、ACC_PUBLIC
、ACC_PRIVATE
、ACC_PROTECTED
、ACC_STATIC
、ACC_FINAL
、ACC_VOLATILE
、ACC_TRANSIENT
フラグのみがserialVersionUID
値の計算に使用されます。コンストラクタおよびメソッド修飾子では、ACC_PUBLIC
、ACC_PRIVATE
、ACC_PROTECTED
、ACC_STATIC
、ACC_FINAL
、ACC_SYNCHRONIZED
、ACC_NATIVE
、ACC_ABSTRACT
、ACC_STRICT
フラグのみが使用されます。名前と記述子は、java.io.DataOutputStream.writeUTF
メソッドが使用する形式で書き込まれます。
ストリーム内の項目の順序は次のとおりです。
クラス名。
クラス修飾子(32ビット整数として書き込まれる)。
各インタフェース名(名前でソート)。
フィールド名でソートされたクラスの各フィールドの場合(
private static
およびprivate transient
フィールドを除く):フィールドの名前。
フィールドの修飾子(32ビット整数として書き込まれる)。
フィールドの記述子。
クラス初期化子が存在する場合は、以下を書き出してください。
メソッドの名前、
<clinit>
。メソッドの修飾子、
java.lang.reflect.Modifier.STATIC
、32ビット整数として書き込まれる。メソッドの記述子、
()V
。
メソッド名とシグニチャでソートされた
private
でない各コンストラクタの場合:メソッドの名前、
<init>
。メソッドの修飾子(32ビット整数として書き込まれる)。
メソッドの記述子。
メソッド名とシグニチャでソートされた
private
でない各メソッドの場合:メソッドの名前。
メソッドの修飾子(32ビット整数として書き込まれる)。
メソッドの記述子。
SHA-1アルゴリズムは、
DataOutputStream
によって作成されたバイト・ストリームに対して実行され、5つの32ビット値からなるsha[0..4]
を作成します。ハッシュ値は、SHA-1メッセージ・ダイジェストの1つ目と2つ目の32ビット値で組み立てられます。メッセージ・ダイジェストの結果である32ビットの5つの語
H0 H1 H2 H3 H4
は、sha
という名前の5つのint
値の配列であり、ハッシュ値は次のように計算されます。
long hash = ((sha[0] >>> 24) & 0xFF) |
((sha[0] >>> 16) & 0xFF) << 8 |
((sha[0] >>> 8) & 0xFF) << 16 |
((sha[0] >>> 0) & 0xFF) << 24 |
((sha[1] >>> 24) & 0xFF) << 32 |
((sha[1] >>> 16) & 0xFF) << 40 |
((sha[1] >>> 8) & 0xFF) << 48 |
((sha[1] >>> 0) & 0xFF) << 56;
5 - 直列化可能オブジェクトのバージョン管理
5.1 概要
Javaオブジェクトが、直列化を使って状態をファイルに保管したり、かたまりとしてデータベースに保管したりする場合、そのデータを読み込むクラスのバージョンがそのデータを書き込んだバージョンと異なる可能性があります。
バージョン管理には、クラスの同一性に関し、いくつかの根本的な問題があります。たとえば、互換性のある変更とは何か、という問題があります。互換性のある変更とは、クラスとその呼出し元との間の規約に影響を与えない変更です。
このセクションでは、目標、前提条件、および解決策について記述します。この解決策は、変更できるものを制限し、メカニズムを慎重に選択することによって、この問題に対処しようとするものです。
ここで示す解決策では、フィールドの追加やクラスの追加によって展開するクラスを「自動的に」処理するメカニズムを示します。直列化では、バージョン管理は、バージョンごとにクラス固有のメソッドを実装することなく行われます。ストリーム形式は、クラス固有のメソッドを呼び出すことなく処理(トラバース)されます。
5.2 目標
目標は次のとおりです。
異なる仮想マシンで稼働する異なるバージョンのクラス間における双方向の通信を、次の方法でサポートする。
Javaクラスが、同じクラスの古いバージョンで書き込まれたストリームを読み込めるようなメカニズムを定義する。
Javaクラスが、同じクラスの古いバージョンで読み込まれることを意図したストリームを書き込めるようなメカニズムを定義する。
永続性とRMIのためのデフォルトの直列化を提供する。
RMIが直列化を使用できるように、パフォーマンスがよくシンプルなケースで簡潔なストリームを作成する。
ストリームを書き込むのに使われたクラスとまったく同じクラスを識別し、ロードできる。
バージョン管理しないクラスに対しオーバーヘッドを低く保つ。
ストリームに保管されているオブジェクト固有のメソッドを呼び出さずに、ストリームの処理(トラバーサル)が可能なストリーム形式を使用する。
5.3 前提
前提条件は次のとおりです。
バージョン管理は、目標を達成するためにストリーム形式を制御する必要があるので、直列化可能クラスだけに適用される。外部化可能クラスは、外部形式に結合されるそれ独自のバージョン管理を行う。
すべてのデータやオブジェクトは、書き込まれた順序でストリームから読み込まれたり、スキップされたりしなければいけない。
クラスは、個別に展開したり、スーパー・タイプやサブタイプと協調して展開する。
クラスは名前で識別される。同じ名前の2つのクラスが異なるバージョンであったり、完全に異なるクラスであったりすることがある。この違いは、それぞれのインタフェースや、それぞれのインタフェースのハッシュを比較すれば区別できる。
デフォルトの直列化では、型の変換は行われない。
ストリーム形式では、線形順序の型変更だけをサポートすればよく、型の任意の分岐をサポートする必要はない。
5.4 ストリームのバージョン管理はだれが行うか
クラスの展開において、非展開クラスによって設定された規約を維持するのは、展開された(後のバージョンの)クラスの責任です。これは、2つの形を取ります。まず、展開されたクラスは、元のバージョンによって与えられたインタフェースに関する既存の前提条件を壊すことはできません。それによって、展開されたクラスを元のクラスのかわりに使用できます。次に、元の(または前の)バージョンと通信するとき、展開されたクラスは、以前のバージョンが非展開クラスの規約を引き続き満たせるだけの、十分で同等な情報を与える必要があります。
展開クラスと非展開クラスおよびそれらのインスタンス間でスーパー・タイプ関係を持つprivateな直列化プロトコルと規約
ここで説明した目的のために、各クラスは、そのスーパー・タイプによって定義されたインタフェースまたは規約を実装し、拡張します。クラスの新しいバージョン、たとえば、foo'
は、foo
のための規約を維持する必要があり、インタフェースを拡張したり、その実装を修正したりできます。
直列化を介したオブジェクト間の通信は、それらのインタフェースによって定義される規約には含まれていません。直列化は、実装間のprivateなプロトコルです。各実装がそのクライアントによって期待される規約に従うように十分なやりとりをすることは、その実装の責任です。
5.5 互換性のあるJavaの型展開
Java言語仕様に、Javaクラスが展開するときのバイナリ互換の説明があります。バイナリ互換の柔軟性のほとんどは、クラス、インタフェース、フィールド、メソッドなどの名前のシンボリック参照を、遅い段階でバインドすることに起因しています。
直列化されたオブジェクト・ストリームのバージョン管理を設計する場合の基本的な項目を、次に示します。
デフォルトの直列化メカニズムは、ストリームのフィールドと、仮想マシンの対応するクラスのフィールドとをバインドするのにシンボリック・モデルを使用します。
ストリーム内で参照される各クラスは、自らのクラス、そのスーパー・タイプ、およびストリームに書き込まれる各直列化可能フィールドの型と名前を一意に識別します。フィールドの順序は、まずプリミティブ型のフィールドがフィールド名でソートされ、次にオブジェクト・フィールドがフィールド名でソートされて決定されます。
各クラスのストリームに出現するデータは、必須データ(オブジェクトの直列化可能フィールドに直接対応する)とオプション・データ(プリミティブやオブジェクトの任意のシーケンスで構成される)の2種類に分けられます。ストリーム形式は、必須データおよびオプション・データがストリーム内でどのように発生するかを定義します。この定義により、必要に応じてクラス全体、必須データまたはオプション・データをスキップすることが可能です。
必須データは、クラス記述子で定義された順序でソートされた、オブジェクトのフィールドで構成されます。
オプション・データは、ストリームに書き込まれ、クラスのフィールドに直接対応しません。クラス自体は、オプション情報の長さ、型、およびバージョン管理を担当します。
クラスに定義されると、
writeObject
/readObject
メソッドはクラスの状態を読み込み/書き込みするためのデフォルトのメカニズムに取って代わります。これらのメソッドは、クラスのオプション・データの読み取りおよび書込みを実行します。必須データの書込みはdefaultWriteObject
の呼出しを介して、必須データの読取りはdefaultReadObject
の呼出しを介して行われます。各クラスのストリーム形式の識別は、ストリーム固有識別子(SUID)を使って行われます。デフォルトでは、ストリーム固有識別子は、クラスのハッシュです。以降のバージョンのクラスでは、すべて、互換性のあるストリーム固有識別子(SUID)を宣言する必要があります。これにより、同じ名前を持つ複数のクラスを、不注意で単一のクラスのバージョンとみなしてしまうことを防げます。
ObjectOutputStream
およびObjectInputStream
のサブタイプには、annotateClass
メソッドを使って、クラスを識別する独自の情報を含めることができます。たとえば、MarshalOutputStream
はクラスのURLを埋め込んでいます。
5.6 直列化に影響する型変更
この概念を使えば、展開するクラスのさまざまなケースに対し、設計上どのように対応するかを説明できます。これらのケースは、クラスのどれかのバージョンによって書き込まれたストリームの観点から記述されます。ストリームが同じクラスの同じバージョンで読み込まれた場合には、情報や機能が失われることはありません。ストリームは、元のクラスに関する唯一の情報源です。そのクラス記述は、それが元のクラス記述のサブセットであるかぎり、そのストリームのデータと、再構成されるクラスのバージョンを一致させるのに十分な情報です。
これらの記述は、クラスの以前のバージョンか以後のバージョンを再構成するためにストリームを読み込む、という観点からのものです。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から非transientへの変更 - 直列化可能フィールドを計算するためにデフォルトの直列化に依存する場合、この変更は、フィールドをクラスに追加するのと同じことである。新しいフィールドはストリームに書き込まれるが、その値はそれより前のクラスによって無視される。これは、直列化によってstaticやtransientのフィールドに値が代入されないためである。
レコード・コンポーネントの追加または削除 - 再構築中のレコード・オブジェクトが、ストリーム内に出現しないレコード・コンポーネントを持つ場合、レコード・クラスの正規コンストラクタにその型のデフォルト値が渡されます。特定の初期化が必要な場合、コンストラクタは、コンポーネントを非デフォルト値に初期化できます。正規コンストラクタに渡されないストリーム・フィールド値は実質的に破棄されます。
クラスの通常クラスからレコード・クラスへの変更 - 通常クラスからレコード・クラスへの変更に適しており、デフォルトの直列化に依存するクラスは、レコード・クラスに変更できます。通常クラスは直接スーパークラスとして
java.lang.Object
を持つ必要があります。そうでない場合、スーパークラス内に直列化可能な状態を持ちません。レコード・クラスのコンポーネントの名前および型は、通常クラスの直列化可能フィールドの名前および型と一致する必要があります。レコード・オブジェクトは、レコード・クラスの正規コンストラクタを介して再構築されます。正規コンストラクタが例外をスローする場合(不変のチェック中など)、InvalidObjectException
がスローされます。- クラスのレコード・クラスから通常クラスへの変更 - デフォルトの直列化に依存するレコード・クラスは、通常クラスに変更できます。通常クラスは、値が前のレコード・クラスの
serialVersionUID
の値と同じである明示的なserialVersionUID
を宣言する必要があります。または、前のレコード・クラスが明示的なserialVersionUID
宣言を持たない場合は、0L
を宣言する必要があります。通常クラスの直列化可能フィールドの名前および型は、前のレコード・クラスのコンポーネントの名前および型と一致する必要があります。
6 - オブジェクト直列化ストリーム・プロトコル
6.1 概要
このストリーム形式は、次の設計目標を実現しました。
- 効率的な読取りを実現するためのコンパクト化および構造化。
- ストリームの構造と形式の知識だけを使ってストリーム内をスキップ可能。クラスごとにコードを呼び出す必要がない。
- データへのストリーム・アクセスだけが必要。
6.2 ストリーム要素
基本構造は、ストリーム内のオブジェクトを表す必要があります。オブジェクトの各属性(クラス、フィールド、およびクラス固有のメソッドによって書き込まれてあとで読み取られるデータ)を表現する必要があります。ストリーム内のオブジェクトの表現は、文法で記述できます。nullオブジェクト、新規オブジェクト、クラス、配列、文字列、およびストリーム内の既存のオブジェクトへの逆参照には、特別な表現があります。ストリームに書き込まれた各オブジェクトには、オブジェクトに逆参照するために使用するハンドルが割り当てられます。ハンドルは、0x7E0000からシーケンシャルに割り当てられます。ストリームがリセットされると、ハンドルはふたたび0x7E0000から始まります。
クラス・オブジェクトは、次の要素によって表されます。
- その
ObjectStreamClass
オブジェクト。
ダイナミック・プロキシ・クラス以外のClassのObjectStreamClass
オブジェクトは、次の要素によって表されます。
互換クラスのストリーム固有識別子(SUID)。
クラスのさまざまなプロパティを表すフラグのセット(クラスが
writeObject
メソッドを定義しているか、クラスが直列化可能、外部化可能、またはenum型か、など)。直列化可能フィールドの数
デフォルトのメカニズムによって直列化された、クラスのフィールドの配列。配列やオブジェクト・フィールドの場合、フィールドの型が文字列(Java仮想マシン仕様に指定されているフィールド記述子形式(
Ljava/lang/Object;
など)でなければいけない)として含まれる。annotateClass
メソッドによって書き込まれるオプションのブロック・データ・レコードまたはオブジェクトスーパー・タイプの
ObjectStreamClass
(スーパー・クラスが直列化可能でなければnull)
ダイナミック・プロキシ・クラスのObjectStreamClass
オブジェクトは、次の要素によって表されます。
ダイナミック・プロキシ・クラスが実装するインタフェース数
ダイナミック・プロキシ・クラスが実装するすべてのインタフェースの名前。そのClassオブジェクト上で
getInterfaces
メソッドを呼び出すことで返される順番にリストする。annotateProxyClass
メソッドによって書き込まれるオプションのブロック・データ・レコードまたはオブジェクト。スーパー・タイプ
java.lang.reflect.Proxy
のObjectStreamClass。
String
オブジェクトの表現は、長さ情報およびModified UTF-8でエンコードされた文字列の内容で構成されます。Modified UTF-8エンコーディングは、Java仮想マシンとjava.io.DataInput
およびDataOutput
インタフェースで使用されるものと同じですが、補助文字とnull文字の表現が標準UTF-8とは異なります。長さ情報の形式は、Modified UTF-8エンコーディング内の文字列の長さに依存します。指定されたString
のModified UTF-8エンコーディング長が65,536バイト未満の場合、長さは符号なし16ビット整数を表す2バイトとして書き込まれます。Java 2 platform, Standard Edition, v1.3以降では、Modified UTF-8エンコーディングでの文字列の長さが65,536バイト以上の場合、長さは符号付き64ビット整数を表す8バイトで書き込まれます。直列化ストリーム内でString
の前にある型コードは、String
の書込みに使用された形式を表しています。
配列は次の要素によって表されます。
その
ObjectStreamClass
オブジェクト。要素数。
値のシーケンス。値の型は配列の型で暗示されます。たとえば、byte配列の値の型はbyteです。
enum定数は次の要素によって表されます。
定数の基底enum型の
ObjectStreamClass
オブジェクト。定数の名前文字列。
ストリーム内の新規オブジェクトは次の要素によって表されます。
オブジェクトのもっとも多く派生されたクラス。
オブジェクトの各直列化可能クラスのデータ(最上位のスーパー・クラスから順に)。各クラスについて、ストリームは次のものを含みます。
直列化可能フィールド。セクション1.5「クラスの直列化可能なフィールドの指定」を参照してください。
クラスが
writeObject
/readObject
メソッドを持つ場合、writeObject
メソッドによって書き込まれたプリミティブ型のオプション・オブジェクトまたはブロック・データ・レコード、あるいはその両方と、endBlockData
がある場合があります。
クラスによって書き込まれたすべてのプリミティブ・データは、バッファリングされてブロック・データ・レコードにラップされます(データがwriteObject
メソッド内でストリームに書き込まれたか、writeObject
メソッドの外部から直接ストリームに書き込まれたのかに関係なく)。このデータは、対応するreadObject
メソッドによって読み込むか、ストリームから直接読み込むことができます。writeObject
メソッドによって書き込まれるオブジェクトは、既存のブロック・データ・レコードを終了し、必要に応じて通常のオブジェクト、null、または逆参照として書き込まれます。ブロック・データ・レコードでは、エラー回復によってオプション・データを破棄できます。クラス内から呼び出された場合には、ストリームはデータやオブジェクトをendBlockData
まで破棄できます。
6.3 ストリーム・プロトコル・バージョン
JDK 1.2での直列化ストリーム形式への変更は、JDK 1.1のすべてのマイナー・リリースと下位互換性のない方法で行う必要がありました。下位互換性が必要な場合に備えるために、直列化ストリームを書き込むときにどのPROTOCOL_VERSION
を使用するかを示す機能が追加されました。ObjectOutputStream.useProtocolVersion
メソッドは、直列化ストリームを書き込むときに使用するプロトコル・バージョンをパラメータとしてとります。
ストリーム・プロトコル・バージョンを次に示します。
ObjectStreamConstants.PROTOCOL_VERSION_1
: 初期ストリーム形式を示します。ObjectStreamConstants.PROTOCOL_VERSION_2
: 新しい外部データ形式を示します。プリミティブ・データはブロック・データ・モードで書き込まれ、TC_ENDBLOCKDATA
で終了します。ブロック・データ境界が標準化されました。ブロック・データ・モードで書き込まれたプリミティブ・データは、1024バイト・チャンクを超えないように正規化されます。この変更の利点は、ストリーム内の直列化データ形式の仕様を厳しくすることでした。この変更は、完全に下位および上位互換です。
JDK 1.2は、デフォルトでPROTOCOL_VERSION_2
を書き込みます。
JDK 1.1は、デフォルトでPROTOCOL_VERSION_1
を書き込みます。
JDK 1.1.7以降は、両方のバージョンを読み取ることができます。
JDK 1.1.7より前のリリースは、PROTOCOL_VERSION_1
のみを読み取れます。
6.4 ストリーム形式の文法
以下の表は、ストリーム形式の文法を示しています。非ターミナル・シンボルは、イタリックで示されます。ターミナル・シンボルは固定幅フォントで示されます。非ターミナルの定義は、その後に「:」が続きます。定義のあとには1つまたは複数の代替定義が続き、それぞれが別の行に示されます。次の表に表記法を示します。
表記法 | 意味 |
---|---|
(データ型) | このトークンにはbyteなどのデータ型が指定される。 |
トークン[n] | このトークンの事前定義オカレンス数(配列)。 |
x0001 | 16進数で表したリテラル値。16進数の数字が値のサイズを表す。 |
<xxx> | 配列の長さを示すために使用される、ストリームから読み込まれた値。 |
シンボル(utf)は、2バイトの長さ情報を使用して書き込まれた文字列を示すために使用され、(long-utf)は、8バイトの長さ情報を使用して書き込まれた文字列を示すために使用されます。詳細は、セクション6.2「ストリーム要素」を参照してください。
6.4.1 文法規則
直列化されたストリームは、ストリーム規則を満たすストリームによって表されます。
stream:
magic version contents
contents:
content
contents content
content:
object
blockdata
object:
newObject
newClass
newArray
newString
newEnum
newClassDesc
prevObject
nullReference
exception
TC_RESET
newClass:
TC_CLASS classDesc newHandle
classDesc:
newClassDesc
nullReference
(ClassDesc)prevObject // an object required to be of type ClassDesc
superClassDesc:
classDesc
newClassDesc:
TC_CLASSDESC className serialVersionUID newHandle classDescInfo
TC_PROXYCLASSDESC newHandle proxyClassDescInfo
classDescInfo:
classDescFlags fields classAnnotation superClassDesc
className:
(utf)
serialVersionUID:
(long)
classDescFlags:
(byte) // Defined in Terminal Symbols and Constants
proxyClassDescInfo:
(int)<count> proxyInterfaceName[count] classAnnotation
superClassDesc
proxyInterfaceName:
(utf)
fields:
(short)<count> fieldDesc[count]
fieldDesc:
primitiveDesc
objectDesc
primitiveDesc:
prim_typecode fieldName
objectDesc:
obj_typecode fieldName className1
fieldName:
(utf)
className1:
(String)object // String containing the field's type,
// in field descriptor format
classAnnotation:
endBlockData
contents endBlockData // contents written by annotateClass
prim_typecode:
'B' // byte
'C' // char
'D' // double
'F' // float
'I' // integer
'J' // long
'S' // short
'Z' // boolean
obj_typecode:
'[' // array
'L' // object
newArray:
TC_ARRAY classDesc newHandle (int)<size> values[size]
newObject:
TC_OBJECT classDesc newHandle classdata[] // data for each class
classdata:
nowrclass // SC_SERIALIZABLE & classDescFlag &&
// !(SC_WRITE_METHOD & classDescFlags)
wrclass objectAnnotation // SC_SERIALIZABLE & classDescFlag &&
// SC_WRITE_METHOD & classDescFlags
externalContents // SC_EXTERNALIZABLE & classDescFlag &&
// !(SC_BLOCKDATA & classDescFlags
objectAnnotation // SC_EXTERNALIZABLE & classDescFlag&&
// SC_BLOCKDATA & classDescFlags
nowrclass:
values // fields in order of class descriptor
wrclass:
nowrclass
objectAnnotation:
endBlockData
contents endBlockData // contents written by writeObject
// or writeExternal PROTOCOL_VERSION_2.
blockdata:
blockdatashort
blockdatalong
blockdatashort:
TC_BLOCKDATA (unsigned byte)<size> (byte)[size]
blockdatalong:
TC_BLOCKDATALONG (int)<size> (byte)[size]
endBlockData:
TC_ENDBLOCKDATA
externalContent: // Only parseable by readExternal
(bytes) // primitive data
object
externalContents: // externalContent written by
externalContent // writeExternal in PROTOCOL_VERSION_1.
externalContents externalContent
newString:
TC_STRING newHandle (utf)
TC_LONGSTRING newHandle (long-utf)
newEnum:
TC_ENUM classDesc newHandle enumConstantName
enumConstantName:
(String)object
prevObject:
TC_REFERENCE (int)handle
nullReference:
TC_NULL
exception:
TC_EXCEPTION reset (Throwable)object reset
magic:
STREAM_MAGIC
version:
STREAM_VERSION
values: // The size and types are described by the
// classDesc for the current object
newHandle: // The next number in sequence is assigned
// to the object being serialized or deserialized
reset: // The set of known objects is discarded
// so the objects of the exception do not
// overlap with the previously sent objects
// or with objects that may be sent after
// the exception
6.4.2 ターミナル・シンボルと定数
java.io.ObjectStreamConstants
の次のシンボルは、ストリームで予期されるターミナル値と定数値を定義します。
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7A;
final static byte TC_EXCEPTION = (byte)0x7B;
final static byte TC_LONGSTRING = (byte) 0x7C;
final static byte TC_PROXYCLASSDESC = (byte) 0x7D;
final static byte TC_ENUM = (byte) 0x7E;
final static int baseWireHandle = 0x7E0000;
フラグbyte classDescFlagsは、次の値を含めることができます。
final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;
フラグSC_WRITE_METHOD
は、ストリームを書き込んでいるSerializableクラスにwriteObject
メソッドがあり、それがストリームに追加データを書き込んだ可能性がある場合に設定されます。この場合、TC_ENDBLOCKDATA
マーカーが常にそのクラスのデータを終了することが期待されます。
フラグSC_BLOCKDATA
は、STREAM_PROTOCOL_2
を使ってExternalizable
クラスがストリームに書き込まれる場合に設定されます。デフォルトでは、これがJDK 1.2でExternalizable
オブジェクトをストリームに書き込むために使用されるプロトコルです。JDK 1.1では、STREAM_PROTOCOL_1
と書きます。
フラグSC_SERIALIZABLE
は、ストリームを書き込んだクラスがjava.io.Serializable
を拡張したけれども、java.io.Externalizable
は拡張しなかった場合に設定されます。そのストリームを読み込むクラスもjava.io.Serializable
を拡張してデフォルト直列化メカニズムを使用しなければいけません。
フラグSC_EXTERNALIZABLE
は、ストリームを書き込んだクラスがjava.io.Externalizable
を拡張した場合に設定されます。そのデータを読み込むクラスもExternalizable
を拡張する必要があり、データはそのwriteExternal
およびreadExternal
メソッドを使って読み込まれます。
フラグSC_ENUM
は、ストリームを書き込んだクラスがenum型だった場合に設定されます。受け取り側の対応するクラスもenum型でなければいけません。enum型の定数のデータは、セクション1.12「Enum定数の直列化」で説明するように読み書きされます。
例
オリジナル・クラスと、リンクされたリスト内の2つのインスタンスの場合を想定します。
class List implements java.io.Serializable {
int value;
List next;
public static void main(String[] args) {
try {
List list1 = new List();
List list2 = new List();
list1.value = 17;
list1.next = list2;
list2.value = 19;
list2.next = null;
ByteArrayOutputStream o = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(o);
out.writeObject(list1);
out.writeObject(list2);
out.flush();
...
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
結果のストリームの内容は次のようになります。
00: ac ed 00 05 73 72 00 04 4c 69 73 74 69 c8 8a 15 >....sr..Listi...<
10: 40 16 ae 68 02 00 02 49 00 05 76 61 6c 75 65 4c >Z......I..valueL<
20: 00 04 6e 65 78 74 74 00 06 4c 4c 69 73 74 3b 78 >..nextt..LList;x<
30: 70 00 00 00 11 73 71 00 7e 00 00 00 00 00 13 70 >p....sq.~......p<
40: 71 00 7e 00 03 >q.~..<