Javaオブジェクト直列化仕様: 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 void writeFields()
        throws IOException;

    public void reset() throws IOException;

    protected void annotateClass(Class<?> cl) throws IOException;

    protected void annotateProxyClass(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.
    public abstract static class PutField
    {
        public void put(String name, boolean value)
            throws IllegalArgumentException;

        public void put(String name, char data)
            throws IllegalArgumentException;

        public void put(String name, byte data)
            throws IllegalArgumentException;

        public void put(String name, short data)
            throws IllegalArgumentException;

        public void put(String name, int data)
            throws IllegalArgumentException;

        public void put(String name, long data)
            throws IllegalArgumentException;

        public void put(String name, float data)
            throws IllegalArgumentException;

        public void put(String name, double data)
            throws IllegalArgumentException;

        public void put(String name, Object data)
            throws 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メソッドが戻ります。

  12. 正規オブジェクトの場合、writeObjectを再帰的に呼び出すことによって、オブジェクトのクラスのObjectStreamClassが書き込まれます。 ストリーム内では、はじめて参照されるときのみ出現します。 このオブジェクト用のハンドルが割り当てられます。

  13. オブジェクトの内容が、ストリームに書き込まれます。

    1. オブジェクトが直列化可能の場合、もっとも直列化可能なクラスが特定されます。 そのクラス、および派生したクラスごとに、そのクラスのフィールドが書き込まれます。 クラスがwriteObjectメソッドを持たない場合は、defaultWriteObjectメソッドが呼び出されて、直列化可能フィールドがストリームに書き込まれます。 クラスがwriteObjectメソッドを持つ場合は、このメソッドが呼び出されます。 defaultWriteObjectまたはputFieldswriteFieldsを呼び出して、オブジェクトの状態を保存してからほかの情報をストリームに書き込んでもかまいません。

    2. オブジェクトが外部化可能な場合、オブジェクトのwriteExternalメソッドが呼び出されます。

    3. オブジェクトが直列化可能でも外部化可能でもない場合、NotSerializableExceptionがスローされます。

例外は、トラバーサル中に発生する場合も、基になるストリーム内で発生する場合もあります。 IOExceptionのサブクラスの場合、例外プロトコルを使って例外がストリームに書き込まれ、ストリーム状態が破棄されます。 最初の例外をストリームに書き込んでいる間に2番目のIOExceptionがスローされた場合、ストリームは不明状態のままになり、writeObjectからStreamCorruptedExceptionがスローされます。 その他の例外の場合、ストリームは中止され、不明で使用不能な状態のままになります。

writeUnsharedメソッドは、「非共有」オブジェクトをObjectOutputStreamに書き込みます。 このメソッドはwriteObjectと同様ですが、指定されたオブジェクトを常にストリーム内で一意な新しいオブジェクトとして書き込みます(以前に直列化されたインスタンスを指す逆参照としてではなく)。 具体的には、次のようになります。

writeUnsharedを介してオブジェクトを記述するのは、オブジェクトを直列化復元する際にオブジェクトへの一意の参照を保証するものではありません。ストリーム内で複数のオブジェクトを定義することができるので、レシーバによる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、"ストリーム・プロトコル・バージョン"」で説明しています。