目次|前|次 | Javaオブジェクト直列化仕様 Version 6.0 |
第2章 |
トピック:
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
メソッドは、オブジェクトをストリームに直列化するために使用します。オブジェクトは、次のように直列化されます。
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.Proxy のisProxyClass メソッドに渡すことによって判別される)を出力するために呼び出されます。その後、表されるクラスの注釈が書き込まれます。クラスがダイナミック・プロキシ・クラスであれば、annotateProxyClass メソッドが呼び出され、そうでない場合はannotateClass メソッドが呼び出されます。writeObject メソッドが戻ります。 |
8. | オブジェクトのクラスまたはObjectInputStream のサブクラス(あるいはその両方)による潜在的な置換を処理します。 |
a. | オブジェクトのクラスがenum型でなく、また適切なwriteReplace メソッドを定義する場合は、そのメソッドが呼び出されます。置換オブジェクトを戻して直列化することもできます。 |
b. | 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. | オブジェクトの内容が、ストリームに書き込まれます。 |
a. | オブジェクトが直列化可能の場合、もっとも直列化可能なクラスが特定されます。そのクラス、および派生したクラスごとに、そのクラスのフィールドが書き込まれます。クラスがwriteObject メソッドを持たない場合は、defaultWriteObject メソッドが呼び出されて、直列化可能フィールドがストリームに書き込まれます。クラスがwriteObject メソッドを持つ場合は、このメソッドが呼び出されます。defaultWriteObject またはputFields とwriteFields を呼び出して、オブジェクトの状態を保存してからほかの情報をストリームに書き込んでもかまいません。 |
b. | オブジェクトが外部化可能な場合、オブジェクトのwriteExternal メソッドが呼び出されます。 |
c. | オブジェクトが直列化可能でも外部化可能でもない場合、NotSerializableException がスローされます。 |
例外は、トラバーサル中に発生する場合も、基になるストリーム内で発生する場合もあります。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ではないので、サブクラスによって直接オーバーライドできます。
クラスPutField
は、あるクラスがデフォルト直列化を使わない場合に、そのクラスの直列化可能フィールドの値を設定するためのAPIを提供します。各メソッドは、指定された名前付き値をストリームに置きます。name
が、フィールドが書き込まれているクラスの直列化可能フィールドの名前と一致しない場合、または指定されたフィールドの型が、呼び出された特定のput
メソッドの2番目のパラメータの型と一致しない場合、IllegalArgumentException
がスローされます。
writeObject
メソッドによって、クラスは独自のフィールドの直列化を制御できます。そのシグニチャを次に示します。
private void writeObject(ObjectOutputStream stream) throws IOException;直列化可能オブジェクトの各サブクラスは、独自の
writeObject
メソッドを定義できます。クラスがメソッドを実装しない場合は、defaultWriteObject
によって与えられるデフォルト直列化が使用されます。実装されている場合、クラスには、そのスーパー・タイプやサブタイプのフィールドではなく、独自のフィールドを書き込む責任だけがあります。
クラスのwriteObject
メソッドには、実装されている場合、クラスの状態を保存する責任があります。ObjectOutputStream
のdefaultWriteObject
メソッドまたはwriteFields
メソッドを一度(一度のみ)呼び出してからでないと、対応するreadObject
メソッドがオブジェクトの状態を復元するために必要なオプション・データを書き込むことはできません。オプション・データを書き込まない場合でも、defaultWriteObject
またはwriteFields
を一度呼び出す必要があります。オプション・データ(ある場合)の書込みの前にdefaultWriteObject
かwriteFields
が呼び出されなければ、ObjectInputStream
がそのwriteObject
メソッドを定義したクラスを解決できない場合に、インスタンス直列化復元の動作は未定義になります。
オプション・データの形式、構造体、バージョン管理の責任は、完全にクラスにあります。
java.io.Externalizable
を実装するオブジェクトは、writeExternal
メソッドを実装して、オブジェクトの状態全体を保存する必要があります。このオブジェクトは、そのスーパー・クラスと協調して、それらの状態を保管しなければいけません。ObjectOutput
のすべてのメソッドを、オブジェクトのプリミティブ型フィールドとオブジェクト・フィールドを保存するために使用できます。
public void writeExternal(ObjectOutput stream) throws IOException;
JDK 1.2で、Externalizableデータを書き込むための新しいデフォルト形式が導入されました。新しい形式は、プリミティブ・データがwriteExternal
メソッドによってブロック・データ・モードで書き込まれるように指定します。さらに、writeExternal
メソッドから戻ったあとに、外部オブジェクトの末尾を示すタグがストリームに付加されます。この形式変更の利点については、セクション3.6「readExternalメソッド」を参照してください。この変更により生じる互換性の問題の詳細は、セクション2.6「useProtocolVersionメソッド」を参照してください。
writeReplace
メソッドは、オブジェクトが書き込まれる前に、オブジェクトのクラスがストリーム内で独自の置換を指定することを許可します。writeReplace
メソッドを実装することにより、クラスは、直列化されている独自のインスタンスの型およびインスタンスを直接制御できます。
ANY-ACCESS-MODIFIER Object writeReplace() { throws ObjectStreamException;
ObjectOutputStream
がストリームにオブジェクトを書き込む準備をしているとき、writeReplace
メソッドが呼び出されます。ObjectOutputStream
は、クラスがwriteReplace
メソッドを定義しているかどうかをチェックします。このメソッドが定義されている場合は、オブジェクトがストリーム内で置換を指定することを許可するためにwriteReplace
メソッドが呼び出されます。返されるオブジェクトは、渡されるオブジェクトと同じ型であるか、または読み込まれて解釈されたオブジェクトが、オブジェクトへのすべての参照と互換性を持つ型のオブジェクトであるべきです。そうでない場合、型の不一致が検出されたときに、ClassCastException
が発生します。
ストリーム・プロトコル・バージョンについては、セクション6.3「ストリーム・プロトコル・バージョン」を参照してください。