レコードの直列化

Java®オブジェクトの直列化仕様の変更 • バージョン16+37-2232

このドキュメントでは、直列化可能レコードをサポートするためのJavaオブジェクト直列化仕様の変更について説明します。レコード・クラス機能の概要は、JEP 395を参照してください。

変更は、JOSSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。


1 - システム・アーキテクチャ

1.1 概要

JavaTMオブジェクトを保存して取り出せることは、もっともtransientなものを除くすべてのアプリケーションを作成するために不可欠です。直列化された形式でのオブジェクトを保存して取り出すために重要なことは、オブジェクトを再構築するために十分なオブジェクトの状態を表現することです。ストリームに保存されるオブジェクトは、SerializableまたはExternalizableインタフェースのどちらかをサポートできます。JavaTMオブジェクトの場合は、直列化された形式が、オブジェクトの内容が保存されたJavaTMクラスを識別および検証し、その内容を新しいインスタンスに復元できなければいけません。直列化可能オブジェクトの場合、ストリームには、ストリーム内のフィールドを互換バージョンのクラスに復元するために十分な情報が取り込まれます。Externalizableオブジェクトの場合、クラスが内容の外部フォーマットに責任を持ちます。

保存および取り出すオブジェクトは、ほかのオブジェクトを参照していることがよくあります。これらのほかのオブジェクトは、オブジェクト間の関係を保持するために、同時に保存および取り出される必要があります。オブジェクトが保存されると、そのオブジェクトから到達可能なすべてのオブジェクトも保存されます。

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定数、およびClassObjectStreamClassString型のオブジェクトには、特別の処理が必要です。その他のオブジェクトは、ストリームに保存したり、そこから取り出したりするためのSerializableまたはExternalizableインタフェースを実装しなければいけません。

プリミティブ・データ型は、DataOutputインタフェースのメソッド(writeIntwriteFloatwriteUTFなど)でストリームに書き込まれます。個別のバイトとバイト配列は、OutputStreamのメソッドで書き込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードでストリームに書き込まれ、各レコードの前にはマーカーとレコード内のバイト数が示されます。

ObjectOutputStreamを拡張して、ストリームのクラスに関する情報をカスタマイズしたり、直列化されるオブジェクトを置き換えることができます。詳細は、annotateClassreplaceObjectメソッドの説明を参照してください。

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インタフェースのメソッド(readIntreadFloatreadUTFなど)から読み込まれます。個別のバイトとバイト配列は、InputStreamのメソッドで読み込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードから読み込まれます。

ObjectInputStreamを拡張して、クラスに関するストリーム内のカスタマイズされた情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳細は、resolveClassresolveObjectメソッドの説明を参照してください。

1.4 コンテナとしてのオブジェクト・ストリーム

オブジェクト直列化は、1つまたは複数のプリミティブやオブジェクトを含むバイト・ストリームを作成し、消費します。ストリームに書き込まれたオブジェクトは、ほかのオブジェクト(これもストリーム内で表現されている)を順に参照します。オブジェクト直列化は、含まれているオブジェクトをエンコードおよび保存するストリーム・フォーマットを1つだけ作成します。

コンテナとして作用する各オブジェクトは、プリミティブやオブジェクトを保存したり取り出したりできるインタフェースを実装します。これらのインタフェースは、ObjectOutputおよびObjectInputインタフェースであり、次のことを行います。

ストリームに保存される各オブジェクトは、保存できることを明示的に示さなければならず、状態の保存と復元に必要なプロトコルを実装しなければいけません。オブジェクト直列化は、そのようなプロトコルを2つ定義しています。これらのプロトコルによって、コンテナは、オブジェクトの状態を書き込んだり、読み込んだりすることをオブジェクトに依頼できます。

オブジェクト・ストリームに保存されるには、各オブジェクトは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クラスの直列化形式をドキュメント化する方法を提供します。

javadocアプリケーションは、javadoc直列化タグを認識し、各SerializableクラスおよびExternalizableクラスの仕様を生成します。これらのタグの使用例については、セクションC.1「java.io.File代替実装の例」を参照してください。

クラスをSerializableとして宣言した場合、オブジェクトの直列化可能状態は、直列化可能フィールド(名前と型による)に加え、オプション・データによって定義されます。オプション・データは、SerializableクラスのwriteObjectメソッドによってのみ明示的に書き込むことができます。オプション・データはSerializableクラスのreadObjectメソッドによって読み込むことができ、直列化は読み込まれないオプション・データをスキップします。

クラスをExternalizableとして宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるように、および以前のバージョンで読み込まれたデータを書き込めるように、独自の展開を処理する必要があります。クラスは、データの保存および復元時には、スーパー・クラスと連携しなければいけません。ストリーム内のスーパークラス・データの位置を指定する必要があります。

Serializableクラスの設計者は、クラスに保存される情報が永続性に適していて、相互運用性および展開のために直列化固有の規則に従っていることを保証する必要があります。クラスの展開の詳細は、第5章「直列化可能オブジェクトのバージョン管理」を参照してください。

1.7 クラスの直列化可能フィールドへのアクセス

直列化は、ストリーム内の直列化可能フィールドにアクセスするための2つのメカニズムを提供します。

Serializableインタフェースを実装し、それ以上のカスタマイズを行わないオブジェクトを読み込みまたは書き込むときには、デフォルトのメカニズムが自動的に使われます。直列化可能フィールドは、クラスの対応するフィールドにマッピングされ、値はそれらのフィールドからストリームに書き込まれるか、または読み込まれてそれぞれ割り当てられます。クラスがwriteObjectおよびreadObjectメソッドを提供する場合は、defaultWriteObjectおよびdefaultReadObjectを呼び出すことによってデフォルト・メカニズムを呼び出すことができます。writeObjectおよびreadObjectメソッドが実装されているときは、直列化可能フィールド値が書き込まれる前または読み込まれたあとに、クラスはそれらを変更する機会を持ちます。

デフォルト・メカニズムを使用できない場合は、直列化可能クラスは、ObjectOutputStreamputFieldsメソッドを使って、直列化可能フィールドの値をストリームに置くことができます。ObjectOutputStreamwriteFieldsメソッドは、値を正しい順序で置いてから、直列化の既存のプロトコルを使ってストリームにそれらの値を書き込みます。同様に、ObjectInputStreamreadFieldsメソッドは、ストリームから値を読み込み、クラスが名前で(かつ任意の順序で)それらを利用できるようにします。直列化可能フィールド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クラスの要件は以下のとおりです。

このクラスは、オプションで次のメソッドを定義できます。

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オブジェクトのクラスの要件は、以下のとおりです。

Externalizableクラスは、オプションで次のメソッドを定義できます。

1.12 Enum定数の直列化

enum定数の直列化は、通常の直列化可能または外部化可能オブジェクトとは異なります。enum定数の直列化された形式を構成するのは、その名前のみです。定数のフィールド値は形式内に存在しません。enum定数を直列化するために、ObjectOutputStreamはenum定数のnameメソッドで返される値を書き込みます。enum定数を直列化復元するために、ObjectInputStreamはストリームから定数名を読み取ります。直列化復元された定数は、定数のenum型と受け取った定数名を引数として渡すjava.lang.Enum.valueOfメソッドを呼び出すことで取得されます。

他の直列化可能または外部化可能オブジェクトと同様に、enum定数は直列化ストリームにその後出現する後方参照のターゲットとして機能できます。

enum定数を直列化するプロセスはカスタマイズできません。enum型で定義されたクラス固有のwriteObjectreadObjectreadObjectNoDatawriteReplacereadResolveメソッドは、直列化および直列化復元の間は無視されます。同様に、serialPersistentFieldsまたはserialVersionUIDフィールド宣言もすべて無視されます。すべてのenum型は0Lの固定されたserialVersionUIDを持ちます。送信されるデータの型にはバリエーションがないため、enum型の直列化可能なフィールドおよびデータをドキュメント化する必要はありません。

1.13 レコードの直列化

これは新しいサブセクションです

レコードは、一般的な直列化可能オブジェクトまたは外部化可能なオブジェクトとは異なる方法で直列化されます。レコード・オブジェクトの直列化形式は、レコード・コンポーネントから導出された値のシーケンスです。レコード・オブジェクトのストリーム形式は、ストリーム内の一般的なオブジェクトの形式と同じです。直列化復元中、指定したストリーム・クラス記述子の等価ローカル・クラスがレコード・クラスである場合、最初にストリーム・フィールドが読み取られて再構築され、レコードのコンポーネント値として機能します。次に、コンポーネント値(または、コンポーネント値がストリーム内に存在しない場合はコンポーネントの型のデフォルト値)を引数として使用してレコードの正規コンストラクタを呼び出すことにより、レコード・オブジェクトが作成されます。

他の直列化可能または外部化可能オブジェクトと同様に、レコード・オブジェクトは直列化ストリームにその後出現する後方参照のターゲットとして機能できます。ただし、コンポーネントによって直接または過渡的にレコード・オブジェクトが参照されているグラフ内の循環は保持されません。レコード・コンポーネントはレコード・コンストラクタの呼出しより前に直列化復元されるため、このような制限になります(セクション1.14「循環参照」を参照してください)。

レコード・オブジェクトが直列化または外部化されるプロセスをカスタマイズすることはできません。直列化または外部化中、レコード・クラスによって定義されたクラス固有のwriteObjectreadObjectreadObjectNoDatawriteExternalおよび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は、直列化復元されると、dDataのインスタンスを参照させます。その結果として、そのobjフィールドにc2の同じインスタンスを参照し戻させます。c2によって参照されるオブジェクトのアイデンティティは、c2.d().objによって参照されるオブジェクトのアイデンティティと同一です(つまり、c2 == c2.d().obj)。

オブジェクトc2の割当て、およびそのハンドルの割当ては、フィールド値(セクション3.1「ObjectInputStreamクラス」のステップ12を参照)の再構築のに行われます。これにより、直列化復元中にフィールド値(および再帰的にそのフィールド値)がc2のハンドルを参照できるようになります。この方法により、一般的なオブジェクトの直列化復元でオブジェクト・グラフの循環をサポートします。

ここで、次のように、Carrierがレコード・クラスであったらどうなるか考えてみます。

    record Carrier(Data d) implements Serializable { }

オブジェクトc2は、直列化復元されると、dDataのインスタンスを参照させます。その結果として、そのobjフィールドに(c2を参照するのではなく) nullを参照させます。直列化復元中、元のオブジェクト・グラフ内のd.objを介した循環参照は保持されません。

レコード・オブジェクトc2の割当て、およびそのハンドルの割当ては、フィールド値(つまり、将来のレコードのコンポーネント値: セクション3.1「ObjectInputStreamクラス」のステップ11を参照)の再構築のに行われます。レコード・コンポーネント値が再構築される前に一連の既知のオブジェクトにレコード・オブジェクトのハンドルが追加される場合、その初期値はnullになります。ハンドルがレコード・オブジェクトに割り当てられるのは、レコード・オブジェクトが(正規コンストラクタの呼出しを介して)構築された後のみです。この結果、レコード・コンポーネント値の直列化復元中、レコード・オブジェクトのハンドルに対するストリーム内の参照では、初期値はnullになります。したがって、直列化復元中、そのコンポーネント(または推移的にそのフィールド)のレコード・オブジェクトを対象とする循環は保持されません。

1.135 機密情報の保護

リソースへの制御アクセスを提供するクラスを開発する場合には、機密性の高い情報と機能が保護されるように注意しなければいけません。直列化復元の際、オブジェクトのprivate状態が復元されます。たとえば、ファイル記述子には、オペレーティング・システム・リソースへのアクセスを提供するハンドルが含まれています。状態の復元はストリームから行われるので、ファイル記述子を偽造できるということは、何らかの不法なアクセスが可能だということです。したがって、直列化の実行時には無難なアプローチを取る必要があり、ストリームにオブジェクトの有効な表現だけが含まれているとは信じないでください。クラスのセキュリティの低下を回避するために、オブジェクトの機密状態がストリームから復元されないようにする必要があり、またはクラスによって再検証される必要があります。クラスの機密データを保護するにはいくつかの技法があります。

もっとも簡単な技法は、機密データを含むフィールドをprivate transientとすることです。transientフィールドは、永続的ではなく、永続性メカニズムによって保存されません。フィールドをこのようにすると、その状態がストリームに現われず、直列化復元の際にも復元されません。(privateフィールドの)書き込みや読込みをクラスの外部で行うことはできないので、クラスのtransientフィールドは安全です。

特に機密性の高いクラスは、一切直列化すべきではありません。これを実現するには、オブジェクトはSerializableExternalizableインタフェースを実装するべきではありません。

クラスによっては、書き込みや読込みは許可するけれども、直列化復元の際に状態を明示的に処理して再検証する方が便利なこともあります。クラスは、適切な状態だけを保管および復元する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" SerializablePermissionputFieldsメソッドまたはwriteUnsharedメソッド、あるいはその両方をオーバーライドするサブクラスのコンストラクタによって直接または間接に呼び出されたときに、これをチェックします。

writeObjectメソッドは、オブジェクトをストリームに直列化するために使用します。オブジェクトは、次のように直列化されます。

  1. サブクラスが実装をオーバーライドする場合は、writeObjectOverrideメソッドを呼び出してから、復帰します。実装のオーバーライドは、このセクションの最後で説明します。

  2. ブロック・データ・バッファにデータがあれば、それがストリームに書き込まれ、バッファがリセットされます。

  3. オブジェクトがnullであれば、nullがストリームに置かれ、writeObjectは復帰します。

  4. ステップ8で説明されているように、オブジェクトが以前に置き換えられていれば、置換えのハンドルをストリームに書き込み、writeObjectは復帰します。

  5. オブジェクトがストリームにすでに書き込まれていれば、そのハンドルがストリームに書き込まれ、writeObjectは復帰します。

  6. オブジェクトがClassであれば、対応するObjectStreamClassがストリームに書き込まれ、そのクラスのハンドルが割り当てられ、writeObjectは復帰します。

  7. オブジェクトがObjectStreamClassである場合、オブジェクトにハンドルが割り当てられ、その後、セクション4.3「直列化された形式」で説明されているクラス記述子形式の1つを使用してストリームに書き込まれます。Version 1.3以降のJava 2 SDK, Standard Editionでは、writeClassDescriptorメソッドがObjectStreamClass (ダイナミック・プロキシ・クラス以外のクラスを表す場合。関連付けられたClassオブジェクトをjava.lang.reflect.ProxyisProxyClassメソッドに渡すことによって判別される)を出力するために呼び出されます。その後、表されるクラスの注釈が書き込まれます。クラスがダイナミック・プロキシ・クラスであれば、annotateProxyClassメソッドが呼び出され、そうでない場合はannotateClassメソッドが呼び出されます。writeObjectメソッドが戻ります。

  8. オブジェクトのクラスまたはObjectInputStreamのサブクラス(あるいはその両方)による潜在的な置換を処理します。

    1. オブジェクトのクラスがenum型でなく、また適切なwriteReplaceメソッドを定義する場合は、そのメソッドが呼び出されます。置換オブジェクトを戻して直列化することもできます。

    2. enableReplaceObjectメソッドの呼出しによりreplaceObjectメソッドが有効に設定されている場合、このメソッドを呼び出すことにより、直列化中のオブジェクトをObjectOutputStreamのサブクラスで置換できます。前のステップで元のオブジェクトを置き換えた場合は、置換オブジェクトでreplaceObjectメソッドが呼び出されます。

    上記の一方または両方のステップで元のオブジェクトを置き換えた場合、元のオブジェクトから置換オブジェクトへのマッピングが(ステップ4で利用するために)記録されます。その後、新規オブジェクトに対し、ステップ3から7が繰り返されます。

    置換オブジェクトが、ステップ3から7を適用できない型の場合、ステップ10で置換オブジェクトを使った処理が再開されます。

  9. オブジェクトがjava.lang.String,の場合、文字列は長さの情報として書き込まれ、その情報のあとにModified UTF-8でエンコードされた文字列の内容が続きます。詳細は、セクション6.2「ストリーム要素」を参照してください。ハンドルが文字列に割り当てられ、writeObjectが戻ります。

  10. オブジェクトが配列の場合、writeObjectが再帰的に呼び出されて、配列のObjectStreamClassが書き込まれます。配列用のハンドルが割り当てられます。そのあとに配列の長さが続きます。その後、配列の各要素がストリームに書き込まれて、writeObjectが戻ります。

  11. オブジェクトがenum定数であれば、writeObjectを再帰的に呼び出すことによって、enum型の定数のObjectStreamClassが書き込まれます。ストリーム内では、はじめて参照されるときのみ出現します。enum定数のハンドルが割り当てられます。次に、ステップ9で説明したように、enum定数のnameメソッドで返された値がStringオブジェクトとして書き込まれます。ストリーム内にすでに同じ名前の文字列が出現した場合には、それへの逆参照が書き込まれます。writeObjectメソッドが戻ります。

  1. オブジェクトがレコード・オブジェクトである場合、レコード・オブジェクトのクラスの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を介してオブジェクトを書き込むこと自体は、直列化復元されたときにそのオブジェクトへの一意参照を保証するものではありませんが、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の信頼できるサブクラスで呼び出すことができます。オブジェクトの置換は、enableReplaceObjecttrue値で呼び出されるまでは、無効になっています。それ以降は、falseに設定することで無効にできます。前の設定が返されます。enableReplaceObjectメソッドは、置換を要求するストリームを信頼できるかどうかを調べます。オブジェクトのprivate状態が意図せずに公開されないことを保証するために、信頼できるストリーム・サブクラスだけがreplaceObjectを使用できます。信頼されるクラスとは、Serializable置換を有効にする権限を保持する、セキュリティ保護ドメインに属するクラスのことです。

ObjectOutputStreamのサブクラスがシステム・ドメインの一部とはみなされない場合、SerializablePermission "enableSubstitution"をセキュリティ・ポリシー・ファイルに追加する必要があります。ObjectInputStreamのサブクラスの保護ドメインに、enableReplaceObject呼出しによる"enableSubstitution"の権限がない場合は、AccessControlExceptionがスローされます。セキュリティ・モデルの詳細は、Javaセキュリティ・アーキテクチャ(JDK1.2)のドキュメントを参照してください。

writeStreamHeaderメソッドは、マジック番号とバージョンをストリームに書き込みます。この情報は、ObjectInputStreamreadStreamHeaderメソッドを使って読み取る必要があります。ストリームの一意形式を識別するために、サブクラスがこのメソッドを実装することが必要な場合があります。

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メソッドには、実装されている場合、クラスの状態を保存する責任があります。ObjectOutputStreamdefaultWriteObjectメソッドまたはwriteFieldsメソッドを一度(一度のみ)呼び出してからでないと、対応するreadObjectメソッドがオブジェクトの状態を復元するために必要なオプション・データを書き込むことはできません。オプション・データを書き込まない場合でも、defaultWriteObjectまたはwriteFieldsを一度呼び出す必要があります。オプション・データ(ある場合)の書込みの前にdefaultWriteObjectwriteFieldsが呼び出されなければ、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メソッドを使用します。このメソッドは、オブジェクトを再構築するためにストリームから読み取ります。

  1. ObjectInputStreamサブクラスが実装をオーバーライドしている場合は、readObjectOverrideメソッドを呼び出し、戻します。実装し直す方法については、このセクションの最後で説明します。

  2. ブロック・データ・レコードがストリーム内で検出された場合は、使用可能なバイト数でBlockDataExceptionをスローします。

  3. ストリーム内のオブジェクトがnullであれば、nullを返します。

  4. ストリーム内のオブジェクトが前のオブジェクトへのハンドルであれば、そのオブジェクトを返します。

  5. ストリーム内のオブジェクトがClassであれば、そのObjectStreamClass記述子を読み取り、それとそのハンドルを既知のオブジェクト・セットに追加し、対応するClassオブジェクトを返します。

  6. ストリーム内のオブジェクトがObjectStreamClassである場合、セクション4.3「直列化された形式」で説明されている形式に応じて、そのデータ内で読み取ります。そのオブジェクトとそのハンドルを、既知のオブジェクト・セットに追加します。Version 1.3以降のJava 2 SDK, Standard Editionでは、readClassDescriptorメソッドがObjectStreamClass (ダイナミック・プロキシ・クラス以外のクラスを表す場合。ストリーム・データで示される)を読み取るために呼び出されます。クラス記述子が動的プロキシ・クラスを表す場合は、記述子のローカル・クラスを取得するためにストリーム上でresolveProxyClassメソッドを呼び出します。そうでない場合は、ローカル・クラスを取得するためにストリーム上でresolveClassメソッドを呼び出します。クラスが解決できない場合は、ClassNotFoundExceptionをスローします。結果として得られるObjectStreamClassオブジェクトを返します。

  7. ストリーム内のオブジェクトがStringの場合、その長さ情報とmodified UTF-8でエンコードされた文字列の内容を読み取ります。詳細は、セクション6.2「ストリーム要素」を参照してください。Stringとそのハンドルを既知のオブジェクトのセットに追加して、ステップ123に進みます。

  8. ストリーム内のオブジェクトが配列であれば、そのObjectStreamClassと配列の長さを読み取ります。配列を割り当て、それとそのハンドルを既知のオブジェクトのセットに追加します。各要素をその型に適したメソッドを使って読み取り、配列に代入します。ステップ123に進みます。

  9. ストリーム内のオブジェクトがenum定数であれば、そのObjectStreamClassとenum定数名を読み取ります。ObjectStreamClassがenum型ではないクラスを表す場合は、InvalidClassExceptionがスローされます。受け取ったObjectStreamClassにバインドされたenum型と受け取った名前と一緒に引数として渡すjava.lang.Enum.valueOfメソッドを呼び出すことで、enum定数への参照を取得します。valueOfメソッドがIllegalArgumentExceptionをスローした場合、IllegalArgumentExceptionを原因としてInvalidObjectExceptionがスローされます。enum定数とそのハンドルを既知のオブジェクトのセットに追加して、ステップ123に進みます。

  10. その他のオブジェクトの場合は、そのオブジェクトのObjectStreamClassがストリームから読み取られます。そのObjectStreamClassのローカル・クラスが取り出されます。このクラスは、直列化可能または外部化可能である必要があります。また、enum型以外である必要があります。クラスがこの条件を満たさない場合は、InvalidClassExceptionがスローされます。

  1. クラスがレコード・クラスである場合。初期値nullを持つハンドルが一連の既知のオブジェクトに追加されます。

    レコード・オブジェクトは、canonicalコンストラクタの呼出しによって構築されます。レコード・クラスR正規コンストラクタは、最初に、R::getRecordComponentsによって返されるレコードRのコンポーネントの数、順序および宣言された型からメソッド記述子を構築してから、この記述子と一致するRの宣言されたコンストラクタを特定することにより、見つけることができます。canonicalコンストラクタが見つからない場合、InvalidClassExceptionがスローされます。

    コンテンツは、次のように復元されます。

    1. ストリームからすべてのフィールド値を読み取って復元します。このストリーム・フィールドを、レコード・コンポーネントを初期化するために使用される適切なコンストラクタ・パラメータのこのフィールドと照合します。この照合は、こストリーム・フィールドの名前とレコード・コンポーネントの名前の同等性に基づいています。これらの名前は同等である必要があります。つまり、Class::getRecordComponentsによって返されるレコード・コンポーネントごとに、最初に、コンポーネントの名前nを確認してから、名前がnと等しいストリーム・フィールドのストリーム値を見つけます。一致したフィールドのストリーム値の具体的な型は、一致したレコード・コンポーネントの具体的な型に割当て可能(Class::isAssignableFrom)である必要があります。そうでない場合、ClassCastExceptionがスローされます。一致したものはすべて型がチェックされます。一致しないストリーム・フィールドは実質的に破棄されます。

    2. 一致したストリーム・フィールド値を使用してレコードの正規コンストラクタを呼び出します。ストリーム・フィールド値は、コンストラクタ・パラメータの対応するレコード・コンポーネントの位置に渡されます。一致しないコンポーネントは、渡された型に適したデフォルト値を持ちます。コンポーネントの呼出しによって例外がスローされた場合、この例外を原因として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`.

プリミティブ型を読む取るためのすべてのメソッドは、ストリームのブロック・データ・レコードからのバイトのみを消費します。ストリーム内の次のアイテムがオブジェクトのときにプリミティブ・データの読込みが行われると、読込みメソッドは-1EOFExceptionのうちで適切な方を返します。プリミティブ型の値は、DataInputStreamによってブロック・データ・レコードから読み込まれます。

スローされる例外は、そのトラバーサル中のエラー、または基になるストリームで起きる例外を反映します。例外がスローされた場合、基になるストリームは不明で使用不能な状態になります。

ストリームでリセット・トークンが起こると、ストリームのすべての状態は破棄されます。既知のオブジェクトのセットはクリアされます。

ストリームで例外トークンが起こると、例外が読み込まれ、終了させた例外を引数として新しいWriteAbortedExceptionがスローされます。ストリーム・コンテキストは前に述べたようにリセットされます。

ストリームから非共有オブジェクトを読み込むには、readUnsharedメソッドを使用します。このメソッドはreadObjectと同じですが、後続のreadObjectおよびreadUnsharedの呼出しが、元のreadUnsharedの呼出しによって返される直列化復元インスタンスへの追加参照を返すことができない点が異なります。具体的には、次のようになります。

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メソッドは、特別に処理されるクラスClassObjectStreamClassString、および配列のオブジェクトの場合は呼び出されません。サブクラスのresolveObjectの実装は、オリジナルの代わりに代入または返される置換オブジェクトを返す場合があります。返されるオブジェクトは、元のオブジェクトのすべての参照と一貫性を持ちそれらに代入可能な型である必要があり、そうでない場合はClassCastExceptionがスローされます。すべての代入は型チェックされます。ストリーム内の、元のオブジェクトへのすべての参照は、置換オブジェクトへの参照によって置き換えられます。

enableResolveObjectメソッドは、直列化復元の際に、あるオブジェクトを監視したり別のオブジェクトに置換したりするために、ObjectOutputStreamの信頼できるサブクラスによって呼び出されます。オブジェクトの置換は、enableResolveObjecttrue値で呼び出されるまで無効になっています。それ以降は、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かどうかには関係なく、オブジェクトの各フィールドの値は、フィールド型のデフォルト値に設定されます。ObjectInputStreamdefaultReadObjectまたはreadFieldsメソッドは、対応するwriteObjectメソッドによって書き出されたオプション・データを読み取る前に、一度のみ呼び出す必要があります。オプション・データを読み取らない場合でも、defaultReadObjectまたはreadFieldsを一度呼び出す必要があります。クラスのreadObjectメソッドが、このクラスのストリームのオプション部分に存在するデータより多くのデータを読み取ろうとすると、ストリームはバイト単位読取りの場合は-1を返し、プリミティブ・データ読取り(readIntreadFloatなど)の場合は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を実装しないクラスのserialVersionUID0Lです。

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インスタンスは、次のプロパティを持ちます。

4.3 直列化された形式

ObjectStreamClassインスタンスの直列化された形式は、その形式が表現するClassオブジェクトが直列化可能であるか、外部化可能であるか、またはダイナミック・プロキシ・クラスであるかによって異なります。

ダイナミック・プロキシ・クラスを表現しないObjectStreamClassインスタンスがストリームに書き込まれるときには、クラス名とserialVersionUID、フラグ、およびフィールド数が書き込まれます。クラスによっては、その他の情報が書き込まれることもあります。

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_PUBLICACC_FINALACC_INTERFACEACC_ABSTRACTフラグを含めることができます。その他のフラグは無視され、serialVersionUIDの計算に影響しません。同様に、フィールド修飾子では、ACC_PUBLICACC_PRIVATEACC_PROTECTEDACC_STATICACC_FINALACC_VOLATILEACC_TRANSIENTフラグのみがserialVersionUID値の計算に使用されます。コンストラクタおよびメソッド修飾子では、ACC_PUBLICACC_PRIVATEACC_PROTECTEDACC_STATICACC_FINALACC_SYNCHRONIZEDACC_NATIVEACC_ABSTRACTACC_STRICTフラグのみが使用されます。名前と記述子は、java.io.DataOutputStream.writeUTFメソッドが使用する形式で書き込まれます。

ストリーム内の項目の順序は次のとおりです。

  1. クラス名。

  2. クラス修飾子(32ビット整数として書き込まれる)。

  3. 各インタフェース名(名前でソート)。

  4. フィールド名でソートされたクラスの各フィールドの場合(private staticおよびprivate transientフィールドを除く):

    1. フィールドの名前。

    2. フィールドの修飾子(32ビット整数として書き込まれる)。

    3. フィールドの記述子。

  5. クラス初期化子が存在する場合は、以下を書き出してください。

    1. メソッドの名前、<clinit>

    2. メソッドの修飾子、java.lang.reflect.Modifier.STATIC、32ビット整数として書き込まれる。

    3. メソッドの記述子、()V

  6. メソッド名とシグニチャでソートされたprivateでない各コンストラクタの場合:

    1. メソッドの名前、<init>

    2. メソッドの修飾子(32ビット整数として書き込まれる)。

    3. メソッドの記述子。

  7. メソッド名とシグニチャでソートされたprivateでない各メソッドの場合:

    1. メソッドの名前。

    2. メソッドの修飾子(32ビット整数として書き込まれる)。

    3. メソッドの記述子。

  8. SHA-1アルゴリズムは、DataOutputStreamによって作成されたバイト・ストリームに対して実行され、5つの32ビット値からなるsha[0..4]を作成します。

  9. ハッシュ値は、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 目標

目標は次のとおりです。

5.3 前提

前提条件は次のとおりです。

5.4 ストリームのバージョン管理はだれが行うか

クラスの展開において、非展開クラスによって設定された規約を維持するのは、展開された(後のバージョンの)クラスの責任です。これは、2つの形を取ります。まず、展開されたクラスは、元のバージョンによって与えられたインタフェースに関する既存の前提条件を壊すことはできません。それによって、展開されたクラスを元のクラスのかわりに使用できます。次に、元の(または前の)バージョンと通信するとき、展開されたクラスは、以前のバージョンが非展開クラスの規約を引き続き満たせるだけの、十分で同等な情報を与える必要があります。

展開クラスと非展開クラスおよびそれらのインスタンス間でスーパー・タイプ関係を持つprivateな直列化プロトコルと規約
展開クラスと非展開クラスおよびそれらのインスタンス間でスーパー・タイプ関係を持つprivateな直列化プロトコルと規約

ここで説明した目的のために、各クラスは、そのスーパー・タイプによって定義されたインタフェースまたは規約を実装し、拡張します。クラスの新しいバージョン、たとえば、foo'は、fooのための規約を維持する必要があり、インタフェースを拡張したり、その実装を修正したりできます。

直列化を介したオブジェクト間の通信は、それらのインタフェースによって定義される規約には含まれていません。直列化は、実装間のprivateなプロトコルです。各実装がそのクライアントによって期待される規約に従うように十分なやりとりをすることは、その実装の責任です。

5.5 互換性のあるJavaの型展開

Java言語仕様に、Javaクラスが展開するときのバイナリ互換の説明があります。バイナリ互換の柔軟性のほとんどは、クラス、インタフェース、フィールド、メソッドなどの名前のシンボリック参照を、遅い段階でバインドすることに起因しています。

直列化されたオブジェクト・ストリームのバージョン管理を設計する場合の基本的な項目を、次に示します。

5.6 直列化に影響する型変更

この概念を使えば、展開するクラスのさまざまなケースに対し、設計上どのように対応するかを説明できます。これらのケースは、クラスのどれかのバージョンによって書き込まれたストリームの観点から記述されます。ストリームが同じクラスの同じバージョンで読み込まれた場合には、情報や機能が失われることはありません。ストリームは、元のクラスに関する唯一の情報源です。そのクラス記述は、それが元のクラス記述のサブセットであるかぎり、そのストリームのデータと、再構成されるクラスのバージョンを一致させるのに十分な情報です。

これらの記述は、クラスの以前のバージョンか以後のバージョンを再構成するためにストリームを読み込む、という観点からのものです。RPCシステムの用語でいえば、これは「受け取り側が正しくする」システムです。書込み側は、そのデータをもっとも適した形式で書き込むので、受け取り側は、その情報を解釈して必要な部分を抽出し、入手できない部分を補う必要があります。

5.6.1 互換性のない変更

クラスに対する互換性のない変更とは、相互運用性の保証が維持できないような変更です。クラスの展開の過程で起こる互換性のない変更には、次のものがあります。

5.6.2 互換性のある変更

クラスの追加<->互換性のあるレコードの変更

クラスへの互換性のある変更は、次のように処理されます。


6 - オブジェクト直列化ストリーム・プロトコル

6.1 概要

このストリーム形式は、次の設計目標を実現しました。

6.2 ストリーム要素

基本構造は、ストリーム内のオブジェクトを表す必要があります。オブジェクトの各属性(クラス、フィールド、およびクラス固有のメソッドによって書き込まれてあとで読み取られるデータ)を表現する必要があります。ストリーム内のオブジェクトの表現は、文法で記述できます。nullオブジェクト、新規オブジェクト、クラス、配列、文字列、およびストリーム内の既存のオブジェクトへの逆参照には、特別な表現があります。ストリームに書き込まれた各オブジェクトには、オブジェクトに逆参照するために使用するハンドルが割り当てられます。ハンドルは、0x7E0000からシーケンシャルに割り当てられます。ストリームがリセットされると、ハンドルはふたたび0x7E0000から始まります。

クラス・オブジェクトは、次の要素によって表されます。

ダイナミック・プロキシ・クラス以外のClassのObjectStreamClassオブジェクトは、次の要素によって表されます。

ダイナミック・プロキシ・クラスの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の書込みに使用された形式を表しています。

配列は次の要素によって表されます。

enum定数は次の要素によって表されます。

ストリーム内の新規オブジェクトは次の要素によって表されます。

クラスによって書き込まれたすべてのプリミティブ・データは、バッファリングされてブロック・データ・レコードにラップされます(データがwriteObjectメソッド内でストリームに書き込まれたか、writeObjectメソッドの外部から直接ストリームに書き込まれたのかに関係なく)。このデータは、対応するreadObjectメソッドによって読み込むか、ストリームから直接読み込むことができます。writeObjectメソッドによって書き込まれるオブジェクトは、既存のブロック・データ・レコードを終了し、必要に応じて通常のオブジェクト、null、または逆参照として書き込まれます。ブロック・データ・レコードでは、エラー回復によってオプション・データを破棄できます。クラス内から呼び出された場合には、ストリームはデータやオブジェクトをendBlockDataまで破棄できます。

6.3 ストリーム・プロトコル・バージョン

JDK 1.2での直列化ストリーム形式への変更は、JDK 1.1のすべてのマイナー・リリースと下位互換性のない方法で行う必要がありました。下位互換性が必要な場合に備えるために、直列化ストリームを書き込むときにどのPROTOCOL_VERSIONを使用するかを示す機能が追加されました。ObjectOutputStream.useProtocolVersionメソッドは、直列化ストリームを書き込むときに使用するプロトコル・バージョンをパラメータとしてとります。

ストリーム・プロトコル・バージョンを次に示します。

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.~..<