目次|前|次 | Javaオブジェクト直列化仕様 Version 6.0 |
第1章
Serializable
またはExternalizable
インタフェースのどちらかをサポートできます。JavaTMオブジェクトの場合は、直列化された形式が、オブジェクトの内容が保存されたJavaTMクラスを識別および検証し、その内容を新しいインスタンスに復元できなければいけません。直列化可能オブジェクトの場合、ストリームには、ストリーム内のフィールドを互換バージョンのクラスに復元するために十分な情報が取り込まれます。Externalizableオブジェクトの場合、クラスが内容の外部フォーマットに責任を持ちます。
保存および取り出すオブジェクトは、ほかのオブジェクトを参照していることがよくあります。これらのほかのオブジェクトは、オブジェクト間の関係を保持するために、同時に保存および取り出される必要があります。オブジェクトが保存されると、そのオブジェクトから到達可能なすべてのオブジェクトも保存されます。
// Serialize today's date to a file. FileOutputStream f = new FileOutputStream("tmp"); ObjectOutput s = new ObjectOutputStream(f); s.writeObject("Today"); s.writeObject(new Date()); s.flush();まず、
OutputStream
(この場合はFileOutputStream
)が、バイトを受け取るために必要です。次に、FileOutputStream
に書き込むObjectOutputStream
が作成されます。そして、文字列「Today」とDateオブジェクトがストリームに書き込まれます。より汎用的に言えば、オブジェクトはwriteObject
メソッドによって書き込まれ、プリミティブはDataOutput
のメソッドによってストリームに書き込まれます。
writeObject
メソッド(セクション2.3「writeObjectメソッド」を参照)は、指定されたオブジェクトを直列化し、オブジェクト・グラフ内のほかのオブジェクトへの参照を再帰的にトラバースして、グラフの完全に直列化された表現を作成します。ストリーム内で、オブジェクトへの最初の参照では、オブジェクトが直列化または外部化され、そのオブジェクトのハンドルが割り当てられます。そのオブジェクトへのそれ以後の参照は、ハンドルとしてエンコードされます。オブジェクト・ハンドルを使用しても、オブジェクト・グラフ内で当然発生する共有参照や循環参照は保持されます。オブジェクトのそれ以後の参照はハンドルだけを使用するため、非常に簡潔な表現が可能になります。
配列、enum定数、およびClass
、ObjectStreamClass
、String
型のオブジェクトには、特別の処理が必要です。その他のオブジェクトは、ストリームに保存したり、そこから取り出したりするためのSerializable
またはExternalizable
インタフェースを実装しなければいけません。
プリミティブ・データ型は、DataOutput
インタフェースのメソッド(writeInt
、writeFloat
、writeUTF
など)でストリームに書き込まれます。個別のバイトとバイト配列は、OutputStream
のメソッドで書き込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードでストリームに書き込まれ、各レコードの前にはマーカーとレコード内のバイト数が示されます。
ObjectOutputStream
を拡張して、ストリームのクラスに関する情報をカスタマイズしたり、直列化されるオブジェクトを置き換えることができます。詳細は、annotateClass
とreplaceObject
メソッドの説明を参照してください。
// Deserialize a string and date from a file. FileInputStream in = new FileInputStream("tmp"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();まず、ソース・ストリームとして、
InputStream
(この場合はFileInputStream
)が必要です。次に、InputStream
から読み込むObjectInputStream
が作成されます。そして、文字列「Today」とDateオブジェクトがストリームから読み込まれます。より汎用的に言えば、オブジェクトはreadObject
メソッドで読み込まれ、プリミティブはDataInput
のメソッドによってストリームから読み込まれます。
readObject
メソッドは、ストリーム内の次のオブジェクトを直列化復元し、ほかのオブジェクトへの参照を再帰的にトラバースして、直列化されたオブジェクトの完全なグラフを作成します。
プリミティブ・データ型は、DataInput
インタフェースのメソッド(readInt
、readFloat
、readUTF
など)から読み込まれます。個別のバイトとバイト配列は、InputStream
のメソッドで読み込まれます。直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードから読み込まれます。
ObjectInputStream
を拡張して、クラスに関するストリーム内のカスタマイズされた情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳細は、resolveClass
とresolveObject
メソッドの説明を参照してください。
コンテナとして作用する各オブジェクトは、プリミティブやオブジェクトを保存したり取り出したりできるインタフェースを実装します。これらのインタフェースは、ObjectOutput
およびObjectInput
インタフェースであり、次のことを行います。
オブジェクト・ストリームに保存されるには、各オブジェクトはSerializable
またはExternalizable
インタフェースを実装しなければいけません。
Serializable
クラスの場合、オブジェクト直列化によって、オブジェクトの各クラスのフィールドが自動的に保存および復元され、フィールドやスーパー・タイプを追加することで展開するクラスが自動的に処理されます。直列化可能クラスは、どのフィールドが保存または復元されるかを宣言し、オプション値やオブジェクトの書き込みや読込みを行うことができます。Externalizable
クラスの場合、オブジェクト直列化は、その外部フォーマット、およびスーパー・タイプの状態がどのように保存および復元されるかの完全な制御をクラスに委譲します。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フィールドへのマッピングを保持しているかぎり)。
serialPersistentFields
は設定できません(ただし、staticメンバー・クラスには設定できます)。内部クラス・インスタンスの直列化に関するその他の制限事項については、セクション1.10「Serializableインタフェース」を参照してください。
@serial
、@serialField
、および@serialData
は、ソース・コード内のSerializableクラスの直列化形式をドキュメント化する方法を提供します。
@serial
タグは、javadocコメント内に置くべきです。構文は以下のとおりです: @serial
field-descriptionオプションのfield-descriptionには、フィールドの意味と設定可能な値を記述します。field-descriptionが複数行にまたがることも可能です。初期リリース後にフィールドを追加した場合は、フィールドが追加されたバージョンを@sinceタグを使って示します。@serial
のfield-descriptionは、直列化固有のドキュメントを提供し、直列化形式ドキュメント内でフィールドのjavadocコメントに付加されます。serialPersistentFields
配列のObjectStreamField
コンポーネントをドキュメント化するには、@serialField
タグを使います。各ObjectStreamField
コンポーネントで、これらのタグのうちの1つを使うべきです。構文は以下のとおりです: @serialField
field-name field-type field-description@serialData
タグは、書き込みまたは読込みが行われるデータの順序および型を記述します。このタグは、writeObject
によって書き込まれたオプション・データ、またはExternalizable.writeExternal
メソッドによって書き込まれたすべてのデータの順序と型を記述します。構文は以下のとおりです: @serialData
data-descriptionクラスをSerializableとして宣言した場合、オブジェクトの直列化可能状態は、直列化可能フィールド(名前と型による)に加え、オプション・データによって定義されます。オプション・データは、Serializable
クラスのwriteObject
メソッドによってのみ明示的に書き込むことができます。オプション・データはSerializable
クラスのreadObject
メソッドによって読み込むことができ、直列化は読み込まれないオプション・データをスキップします。
クラスをExternalizableとして宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるように、および以前のバージョンで読み込まれたデータを書き込めるように、独自の展開を処理する必要があります。クラスは、データの保存および復元時には、スーパー・クラスと連携しなければいけません。ストリーム内のスーパークラス・データの位置を指定する必要があります。
Serializableクラスの設計者は、クラスに保存される情報が永続性に適していて、相互運用性および展開のために直列化固有の規則に従っていることを保証する必要があります。クラスの展開の詳細については、第5章「直列化可能オブジェクトのバージョン管理」を参照してください。
Serializable
インタフェースを実装し、それ以上のカスタマイズを行わないオブジェクトを読み込みまたは書き込むときには、デフォルトのメカニズムが自動的に使われます。直列化可能フィールドは、クラスの対応するフィールドにマッピングされ、値はそれらのフィールドからストリームに書き込まれるか、または読み込まれてそれぞれ割り当てられます。クラスがwriteObject
およびreadObject
メソッドを提供する場合は、defaultWriteObject
およびdefaultReadObject
を呼び出すことによってデフォルト・メカニズムを呼び出すことができます。writeObject
およびreadObject
メソッドが実装されているときは、直列化可能フィールド値が書き込まれる前または読み込まれたあとに、クラスはそれらを変更する機会を持ちます。
デフォルト・メカニズムを使用できない場合は、直列化可能クラスは、ObjectOutputStream
のputFields
メソッドを使って、直列化可能フィールドの値をストリームに置くことができます。ObjectOutputStream
のwriteFields
メソッドは、値を正しい順序で置いてから、直列化の既存のプロトコルを使ってストリームにそれらの値を書き込みます。同様に、ObjectInputStream
のreadFields
メソッドは、ストリームから値を読み込み、クラスが名前で(かつ任意の順序で)それらを利用できるようにします。直列化可能フィールドAPIの詳細は、セクション2.2「ObjectOutputStream.PutFieldクラス」およびセクション3.2「ObjectInputStream.GetFieldクラス」を参照してください。
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
メソッドは、オブジェクトを書き込むために使用します。スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージに書き込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが破損している可能性があります。このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
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
メソッドは、オブジェクトを読み込んで返すために使用します。スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージから読み込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが破損している可能性があります。このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
Serializable
インタフェースは、直列化可能プロトコルを実装するクラスを識別するために定義されます。
package java.io; public interface Serializable {};Serializableクラスの要件は以下のとおりです。
serialPersistentFields
メンバーを使ってフィールドを明示的に直列化可能と宣言するか、transientキーワードを使って非直列化可能フィールドを指定する)writeObject
メソッド - どの情報を保存するか制御したり、追加情報をストリームに付加したりするreadObject
メソッド - 対応するwriteObject
メソッドで書き込まれた情報を読み込んだり、復元後のオブジェクトの状態を更新したりするwriteReplace
メソッド - クラスが、ストリームに書き込まれる置換オブジェクトを指定できるObjectOutputStream
およびObjectInputStream
を使用すると、操作対象の直列化可能クラスを展開できます(以前のバージョンのクラスとの互換性を持つクラスへの変更が可能)。互換性を保つ変更に使用できるメカニズムについての詳細は、セクション5.5「互換性のあるJavaTMの型展開」を参照してください。
javac
(またはその他のJavaTMコンパイラ)によって生成された合成フィールドは、実装に依存するので、コンパイラによって相違が生じることがあります。そのようなフィールドの相違により、デフォルトのserialVersionUID
値が競合するのみでなく、互換性が損なわれる可能性があります。ローカルおよび匿名の内部クラスに割り当てられる名前も実装に依存するので、コンパイラによって相違が生じる可能性があります。内部クラスは、コンパイル時定数フィールド以外のstaticメンバーを宣言できないので、serialPersistentFields
メカニズムを使用して直列化可能フィールドを指定できません。さらに、外部インスタンスに関連付けられた内部クラスは、引数なしのコンストラクタ(そのような内部クラスのコンストラクタは、内包するインスタンスを付加パラメータとして暗黙的に受け入れる)を持たないため、Externalizable
を実装できません。ただし、前述の問題はいずれも、staticメンバー・クラスには当てはまりません。
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オブジェクトのクラスの要件は、以下のとおりです。
writeExternal
およびreadExternal
メソッドはpublicであり、クライアントが、メソッドとフィールドを使わずにオブジェクト内で情報を書き込んだり読み込んだりできる危険性があります。これらのメソッドを使うのは、オブジェクトが保持する情報が機密でないとき、またはそれを公開してもセキュリティ・リスクが発生しないときだけにしなければいけません。
Externalizable
インタフェース・メカニズムを内部クラスに使用できません。内部クラスを直列化する必要がある場合には、Serializable
インタフェースを実装するようにしてください。ただし、直列化可能な内部クラスの場合でも、いくつかの制限事項があります。その詳細は、1.10項「Serializableインタフェース」を参照してください。
ObjectOutputStream
はenum定数のname
メソッドで返される値を書き込みます。enum定数を直列化復元するために、ObjectInputStream
はストリームから定数名を読み取ります。直列化復元された定数は、定数のenum型と受け取った定数名を引数として渡すjava.lang.Enum.valueOf
メソッドを呼び出すことで取得されます。他の直列化可能または外部化可能オブジェクトと同様に、enum定数は直列化ストリームにその後出現する後方参照のターゲットとして機能できます。
enum定数を直列化するプロセスはカスタマイズできません。enum型で定義されたクラス固有のwriteObject
、readObject
、readObjectNoData
、writeReplace
、readResolve
メソッドは、直列化および直列化復元の間は無視されます。同様に、serialPersistentFields
またはserialVersionUID
フィールド宣言もすべて無視されます。すべてのenum型は0L
の固定されたserialVersionUID
を持ちます。送信されるデータの型にはバリエーションがないため、enum型の直列化可能なフィールドおよびデータをドキュメント化する必要はありません。
もっとも簡単な技法は、機密データを含むフィールドをprivate transientとすることです。transientフィールドは、永続的ではなく、永続性メカニズムによって保存されません。フィールドをこのようにすると、その状態がストリームに現われず、直列化復元の際にも復元されません。(privateフィールドの)書き込みや読込みをクラスの外部で行うことはできないので、クラスのtransientフィールドは安全です。
特に機密性の高いクラスは、一切直列化すべきではありません。これを実現するには、オブジェクトはSerializable
やExternalizable
インタフェースを実装するべきではありません。
クラスによっては、書き込みや読込みは許可するけれども、直列化復元の際に状態を明示的に処理して再検証する方が便利なこともあります。クラスは、適切な状態だけを保管および復元するwriteObject
およびreadObject
メソッドを実装すべきです。アクセスを拒否すべき場合には、NotSerializableException
をスローすることで、それ以上のアクセスを防ぎます。