- 概要
- オブジェクト・ストリームへの書き込み
- オブジェクト・ストリームからの読み取り
- コンテナとしてのオブジェクト・ストリーム
- クラスの直列化可能フィールドの定義
- クラスの直列化可能フィールドおよびデータの文書化
- クラスの直列化可能フィールドへのアクセス
- ObjectOutputインタフェース
- ObjectInputインタフェース
- Serializableインタフェース
- Externalizableインタフェース
- Enum定数の直列化
- レコードの直列化
- 循環参照
- 機密情報の保護
1.1 概要
JavaTMオブジェクトを保存して取り出せることは、もっともtransientなものを除くすべてのアプリケーションを作成するために不可欠です。 直列化された形式でのオブジェクトを保存して取り出すために重要なことは、オブジェクトを再構築するために十分なオブジェクトの状態を表現することです。 ストリームに保存されるオブジェクトは、Serializable
またはExternalizable
インタフェースのどちらかをサポートできます。 JavaTMオブジェクトの場合は、直列化された形式が、オブジェクトの内容が保存されたJavaTMクラスを識別および検証し、その内容を新しいインスタンスに復元できなければいけません。 直列化可能オブジェクトの場合、ストリームには、ストリーム内のフィールドを互換バージョンのクラスに復元するために十分な情報が取り込まれます。 Externalizableオブジェクトの場合、クラスが内容の外部フォーマットに責任を持ちます。
保存および取り出すオブジェクトは、ほかのオブジェクトを参照していることがよくあります。 これらのほかのオブジェクトは、オブジェクト間の関係を保持するために、同時に保存および取り出される必要があります。 オブジェクトが保存されると、そのオブジェクトから到達可能なすべてのオブジェクトも保存されます。
JavaTMオブジェクトの直列化の目標は次のとおりです。
- シンプルで拡張性のあるメカニズムにする。
- JavaTMオブジェクトの型および安全属性を直列化形式でも保持する。
- リモート・オブジェクトの必要に応じて、整列化と非整列化をサポートできるようにする。
- JavaTMオブジェクトのシンプルな永続性をサポートできるようにする。
- クラスごとの実装を要求する(カスタマイズ目的のみ)。
- オブジェクトが外部フォーマットを定義できる。
1.2 オブジェクト・ストリームへの書き込み
オブジェクトやプリミティブをストリームに書き込むことは、簡単な処理です。 次に例を示します。
// Serialize today's date to a file.
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();
まず、OutputStream
(この場合はFileOutputStream
)が、バイトを受け取るために必要です。 次に、FileOutputStream
に書き込むObjectOutputStream
が作成されます。 そして、文字列「Today」とDateオブジェクトがストリームに書き込まれます。 より汎用的に言えば、オブジェクトはwriteObject
メソッドによって書き込まれ、プリミティブはDataOutput
のメソッドによってストリームに書き込まれます。
writeObject
メソッド(「セクション2.3、"writeObjectメソッド"」を参照してください)は、指定されたオブジェクトを直列化し、オブジェクト・グラフ内の他のオブジェクトへの参照を再帰的にトラバースして、グラフの完全な直列化表現を作成します。 ストリーム内で、オブジェクトへの最初の参照では、オブジェクトが直列化または外部化され、そのオブジェクトのハンドルが割り当てられます。 そのオブジェクトへのそれ以後の参照は、ハンドルとしてエンコードされます。 ハンドルの使用により、オブジェクト・グラフ内で自然に発生するオブジェクトの共有が保持され、オブジェクト間の循環参照(つまり、グラフ内での循環)が可能になります。
配列、enum定数、およびClass
、ObjectStreamClass
、String
型のオブジェクトには、特別の処理が必要です。 その他のオブジェクトは、ストリームに保存したり、そこから取り出したりするためのSerializable
またはExternalizable
インタフェースを実装しなければいけません。
プリミティブ・データ型は、DataOutput
インタフェースのメソッド(writeInt
、writeFloat
、writeUTF
など)でストリームに書き込まれます。 個別のバイトとバイト配列は、OutputStream
のメソッドで書き込まれます。 直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードでストリームに書き込まれ、各レコードの前にはマーカーとレコード内のバイト数が示されます。
ObjectOutputStream
を拡張して、ストリームのクラスに関する情報をカスタマイズしたり、直列化されるオブジェクトを置き換えることができます。 詳細は、annotateClass
とreplaceObject
メソッドの説明を参照してください。
1.3 オブジェクト・ストリームからの読み取り
書き込みと同様、ストリームからのオブジェクトの読み込みも、簡単なことです。
// Deserialize a string and date from a file.
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
まず、ソース・ストリームとして、InputStream
(この場合はFileInputStream
)が必要です。 次に、InputStream
から読み込むObjectInputStream
が作成されます。 そして、文字列「Today」とDateオブジェクトがストリームから読み込まれます。 より汎用的に言えば、オブジェクトはreadObject
メソッドで読み込まれ、プリミティブはDataInput
のメソッドによってストリームから読み込まれます。
readObject
メソッドは、ストリーム内の次のオブジェクトを直列化復元し、他のオブジェクトへの参照を再帰的にトラバースして、直列化されたオブジェクトの完全なグラフを作成します。
プリミティブ・データ型は、DataInput
インタフェースのメソッド(readInt
、readFloat
、readUTF
など)から読み込まれます。 個別のバイトとバイト配列は、InputStream
のメソッドで読み込まれます。 直列化可能フィールド以外のプリミティブ・データはブロック・データ・レコードから読み込まれます。
ObjectInputStream
を拡張して、クラスに関するストリーム内のカスタマイズされた情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。 詳細は、resolveClass
とresolveObject
メソッドの説明を参照してください。
1.4 コンテナとしてのオブジェクト・ストリーム
オブジェクト直列化は、1つまたは複数のプリミティブやオブジェクトを含むバイト・ストリームを作成し、消費します。 ストリームに書き込まれたオブジェクトは、ほかのオブジェクト(これもストリーム内で表現されている)を順に参照します。 オブジェクト直列化は、含まれているオブジェクトをエンコードおよび保存するストリーム・フォーマットを1つだけ作成します。
コンテナとして作用する各オブジェクトは、プリミティブやオブジェクトを保存したり取り出したりできるインタフェースを実装します。 これらのインタフェースは、ObjectOutput
およびObjectInput
インタフェースであり、次のことを行います。
- 書込み先のストリームや読込み元のストリームを提供する
- プリミティブ型やオブジェクトをストリームに書き込む要求を処理する
- プリミティブ型やオブジェクトをストリームから読み込む要求を処理する
ストリームに保存される各オブジェクトは、保存できることを明示的に示さなければならず、状態の保存と復元に必要なプロトコルを実装しなければいけません。 オブジェクト直列化は、そのようなプロトコルを2つ定義しています。 これらのプロトコルによって、コンテナは、オブジェクトの状態を書き込んだり、読み込んだりすることをオブジェクトに依頼できます。
オブジェクト・ストリームに保存されるには、各オブジェクトはSerializable
またはExternalizable
インタフェースを実装しなければいけません。
Serializable
クラスの場合、オブジェクト直列化によって、オブジェクトの各クラスのフィールドが自動的に保存および復元され、フィールドやスーパー・タイプを追加することで展開するクラスが自動的に処理されます。 直列化可能クラスは、どのフィールドが保存または復元されるかを宣言し、オプション値やオブジェクトの書き込みや読込みを行うことができます。Externalizable
クラスの場合、オブジェクト直列化は、その外部フォーマット、およびスーパー・タイプの状態がどのように保存および復元されるかの完全な制御をクラスに委譲します。
1.5 クラスの直列化可能フィールドの定義
クラスの直列化可能フィールドは、2つの方法で定義できます。 クラスのデフォルト直列化可能フィールドは、非transientおよび非staticフィールドとして定義されます。 このデフォルト計算は、Serializable
クラスに特別なフィールドserialPersistentFields
を宣言することによってオーバーライドできます。 このフィールドは、直列化可能フィールドの名前および型を一覧表示するObjectStreamField
オブジェクトの配列を使って、初期化する必要があります。 フィールドの修飾子は、private、static、およびfinalにする必要があります。 フィールドの値がnullであるか、ObjectStreamField[]
のインスタンスではない場合、またはフィールドが必須の修飾子を持たない場合は、フィールドがまったく宣言されていない場合と同じ動作になります。
たとえば、次の宣言は、デフォルトの動作を複製します。
class List implements Serializable {
List next;
private static final ObjectStreamField[] serialPersistentFields
= {new ObjectStreamField("next", List.class)};
}
serialPersistentFields
を使用してクラスのSerializableフィールドを定義することによって、直列化可能フィールドはSerializable
クラスの現在の定義内のフィールドでなければいけないという制限がなくなります。 Serializable
クラスのwriteObject
およびreadObject
メソッドは、クラスの現在の実装を、「セクション1.7、"クラスの直列化可能フィールドへのアクセス"」で説明されているインタフェースを使用して、クラスの直列化可能なフィールドにマップできます。 したがって、Serializable
クラスのフィールドはあとのリリースで変更できます(リリース境界をまたがって互換性を持つ必要があるSerializableフィールドへのマッピングを保持しているかぎり)。
ノート: ただし、このメカニズムを使用して内部クラスの直列化可能なフィールドを指定することには制限があります。 内部クラスは、定数または定数から成る式に初期化された、final staticフィールドのみ含むことができます。 結果として、内部クラスにserialPersistentFields
は設定できません(ただし、staticメンバー・クラスには設定できます)。 内部クラス・インスタンスの直列化に関するその他の制限については、section 「セクション1.10、"直列化可能インタフェース"」を参照してください。
1.6 クラスの直列化可能フィールドおよびデータの文書化
Serializableクラスの代替実装との相互運用性を可能にするためにクラスの直列化可能状態をドキュメント化し、クラス展開をドキュメント化することは重要です。 直列化可能フィールドをドキュメント化することは、フィールドが直列化可能かどうかを確認する最終機会を与えることになります。 javadoc直列化タグ、@serial
、@serialField
、および@serialData
は、ソース・コード内のSerializableクラスの直列化形式をドキュメント化する方法を提供します。
デフォルト直列化可能フィールドの
@serial
タグは、javadocコメント内に置くべきです。 構文は以下のとおりです:@serial
field-descriptionオプションのfield-descriptionには、フィールドの意味と設定可能な値を記述します。 field-descriptionが複数行にまたがることも可能です。 最初のリリースの後にフィールドが追加されると、@sinceタグはフィールドが追加されたバージョンを示します。@serial
のfield-descriptionは、直列化固有のドキュメントを提供し、直列化形式ドキュメント内でフィールドのjavadocコメントに付加されます。serialPersistentFields
配列のObjectStreamField
コンポーネントをドキュメント化するには、@serialField
タグを使います。 各ObjectStreamField
コンポーネントで、これらのタグのうちの1つを使うべきです。 構文は以下のとおりです:@serialField
field-name field-type field-description@serialData
タグは、書き込みまたは読込みが行われるデータの順序および型を記述します。 このタグは、writeObject
によって書き込まれたオプション・データ、またはExternalizable.writeExternal
メソッドによって書き込まれたすべてのデータの順序と型を記述します。 構文は以下のとおりです:@serialData
data-description
javadocアプリケーションは、javadoc直列化タグを認識し、各SerializableクラスおよびExternalizableクラスの仕様を生成します。 これらのタグを使用する例については、「セクションC.1、"java.io.Fileの代替実装の例"」を参照してください。
クラスをSerializableとして宣言した場合、オブジェクトの直列化可能状態は、直列化可能フィールド(名前と型による)に加え、オプション・データによって定義されます。 オプション・データは、Serializable
クラスのwriteObject
メソッドによってのみ明示的に書き込むことができます。 オプション・データはSerializable
クラスのreadObject
メソッドによって読み込むことができ、直列化は読み込まれないオプション・データをスキップします。
クラスをExternalizableとして宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。 クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。 クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるように、および以前のバージョンで読み込まれたデータを書き込めるように、独自の展開を処理する必要があります。 クラスは、データの保存および復元時には、スーパー・クラスと連携しなければいけません。 ストリーム内のスーパークラス・データの位置を指定する必要があります。
Serializableクラスの設計者は、クラスに保存される情報が永続性に適していて、相互運用性および展開のために直列化固有の規則に従っていることを保証する必要があります。 クラスの進化については、「第5章、"直列化可能オブジェクトのバージョン管理"」で詳しく説明しています。
1.7 クラスの直列化可能フィールドへのアクセス
直列化は、ストリーム内の直列化可能フィールドにアクセスするための2つのメカニズムを提供します。
- デフォルトのメカニズムはカスタマイズが不要
- SerializableフィールドAPIを使って、クラスは名前と型によって直列化可能フィールドを明示的にアクセス/設定できる
Serializable
インタフェースを実装し、それ以上のカスタマイズを行わないオブジェクトを読み込みまたは書き込むときには、デフォルトのメカニズムが自動的に使われます。 直列化可能フィールドは、クラスの対応するフィールドにマッピングされ、値はそれらのフィールドからストリームに書き込まれるか、または読み込まれてそれぞれ割り当てられます。 クラスがwriteObject
およびreadObject
メソッドを提供する場合は、defaultWriteObject
およびdefaultReadObject
を呼び出すことによってデフォルト・メカニズムを呼び出すことができます。 writeObject
およびreadObject
メソッドが実装されているときは、直列化可能フィールド値が書き込まれる前または読み込まれたあとに、クラスはそれらを変更する機会を持ちます。
デフォルト・メカニズムを使用できない場合は、直列化可能クラスは、ObjectOutputStream
のputFields
メソッドを使って、直列化可能フィールドの値をストリームに置くことができます。 ObjectOutputStream
のwriteFields
メソッドは、値を正しい順序で置いてから、直列化の既存のプロトコルを使ってストリームにそれらの値を書き込みます。 同様に、ObjectInputStream
のreadFields
メソッドは、ストリームから値を読み込み、クラスが名前で(かつ任意の順序で)それらを利用できるようにします。 Serializable Fields APIの詳細については、「セクション2.2、"ObjectOutputStream.PutFieldクラス"」および「セクション3.2、"ObjectInputStream.GetFieldクラス"」を参照してください。
1.8 ObjectOutputインタフェース
ObjectOutput
インタフェースは、オブジェクト・ストレージへのabstractストリーム・ベース・インタフェースを提供します。 このインタフェースはDataOutputインタフェースの拡張なので、それらのメソッドを使ってプリミティブ・データ型を書き込むことができます。 このインタフェースを実装するオブジェクトを使えば、プリミティブやオブジェクトを格納できます。
package java.io;
public interface ObjectOutput extends DataOutput
{
public void writeObject(Object obj) throws IOException;
public void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
public void flush() throws IOException;
public void close() throws IOException;
}
The
writeObject
メソッドは、オブジェクトを書き込むために使用します。 スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージに書き込んでいるときに起こった例外を表しています。 何らかの例外がスローされた場合、そのストレージが破損している可能性があります。 このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
1.9 ObjectInputインタフェース
ObjectInput
インタフェースは、オブジェクト取り出しへのabstractストリーム・ベース・インタフェースを提供します。 DataInput
インタフェースの拡張なので、このインタフェースのプリミティブ・データ型を読み込むメソッドを利用できます。
package java.io;
public interface ObjectInput extends DataInput
{
public Object readObject()
throws ClassNotFoundException, IOException;
public int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
public long skip(long n) throws IOException;
public int available() throws IOException;
public void close() throws IOException;
}
readObject
メソッドは、オブジェクトを読み込んで返すために使用します。 スローされる例外は、オブジェクトやそのフィールドにアクセスしているときに起こったエラーか、ストレージから読み込んでいるときに起こった例外を表しています。 何らかの例外がスローされた場合、そのストレージが破損している可能性があります。 このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
1.10 Serializableインタフェース
オブジェクトの直列化を行うと、保存されるオブジェクトのJavaTMクラスに関する情報を持つストリームが作成されます。 直列化可能オブジェクトの場合、これらのオブジェクトを復元するのに十分な情報が保持されます(クラスの異なる(しかし互換性のある)バージョンの実装が存在している場合でも)。 Serializable
インタフェースは、直列化可能プロトコルを実装するクラスを識別するために定義されます。
package java.io;
public interface Serializable {};
Serializableクラスの要件は以下のとおりです。
java.io.Serializable
インタフェースを実装直列化可能にするべきフィールドを特定する
(
serialPersistentFields
メンバーを使ってフィールドを明示的に直列化可能と宣言するか、transientキーワードを使って非直列化可能フィールドを指定する)最初の直列化可能でないスーパー・クラスの、引数のないコンストラクタにアクセスできる
このクラスは、オプションで次のメソッドを定義できます。
writeObject
メソッド - どの情報を保存するか制御したり、追加情報をストリームに付加したりするreadObject
メソッド - 対応するwriteObject
メソッドで書き込まれた情報を読み込んだり、復元後のオブジェクトの状態を更新したりするwriteReplace
メソッド - クラスが、ストリームに書き込まれる置換オブジェクトを指定できる(詳細については、「セクション2.5、"writeReplaceメソッド"」を参照してください。)
readResolve
メソッド - クラスが、ストリームから読み込まれたばかりのオブジェクトの置換オブジェクトを指定できる(詳細については、「セクション3.7、"readResolveメソッド」を参照してください。)
ObjectOutputStream
およびObjectInputStream
を使用すると、操作対象の直列化可能クラスを展開できます(以前のバージョンのクラスとの互換性を持つクラスへの変更が可能)。 互換性のある変更を可能にするためのメカニズムについては、「セクション5.5、"互換性のあるJava型の進化"」を参照してください。
ノート: ローカル・クラスと匿名クラスを含む内部クラス(つまり、静的メンバー・クラスではないネストされたクラス)の直列化は、いくつかの理由で強く非推奨とされています。 非staticコンテキストで宣言された内部クラスには、内包するクラス・インスタンスへの暗黙的な非transient参照が含まれるので、そのような内部クラス・インスタンスを直列化すると、関連する外部クラス・インスタンスも直列化することになります。 内部クラスを実装するためにjavac
(またはその他のJavaTMコンパイラ)によって生成された合成フィールドは、実装に依存するので、コンパイラによって相違が生じることがあります。そのようなフィールドの相違により、デフォルトのserialVersionUID
値が競合するのみでなく、互換性が損なわれる可能性があります。 ローカルおよび匿名の内部クラスに割り当てられる名前も実装に依存するので、コンパイラによって相違が生じる可能性があります。 内部クラスは、コンパイル時定数フィールド以外のstaticメンバーを宣言できないので、serialPersistentFields
メカニズムを使用して直列化可能フィールドを指定できません。 さらに、外部インスタンスに関連付けられた内部クラスは、引数なしのコンストラクタ(そのような内部クラスのコンストラクタは、内包するインスタンスを付加パラメータとして暗黙的に受け入れる)を持たないため、Externalizable
を実装できません。 ただし、前述の問題はいずれも、staticメンバー・クラスには当てはまりません。
1.11 Externalizableインタフェース
Externalizableオブジェクトの場合、オブジェクトのクラスの識別情報のみがコンテナによって保存されます。クラスが内容を保存して復元する必要があります。 Externalizable
インタフェースは、次のように定義されます。
package java.io;
public interface Externalizable extends Serializable
{
public void writeExternal(ObjectOutput out)
throws IOException;
public void readExternal(ObjectInput in)
throws IOException, java.lang.ClassNotFoundException;
}
Externalizableオブジェクトのクラスの要件は、以下のとおりです。
java.io.Externalizable
インタフェースを実装writeExternal
メソッドを実装してオブジェクトの状態を保存する(その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)
readExternal
メソッドを実装して、writeExternal
メソッドによって書き込まれたデータをストリームから読み取って、オブジェクトの状態を復元する(その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)
writeExternal
とreadExternal
メソッドのみがフォーマットに責任を持つ(外部で定義されたフォーマットが書き込まれる場合)ノート:
writeExternal
メソッドとreadExternal
メソッドは公開されており、メソッドやフィールドを使用する以外に、クライアントがオブジェクトの情報をオブジェクトに書き込んだり読み取ったりするリスクが高くなります。 これらのメソッドを使うのは、オブジェクトが保持する情報が機密でないとき、またはそれを公開してもセキュリティ・リスクが発生しないときだけにしなければいけません。publicで引数なしのコンストラクタを持つ
ノート: そのようなクラスのコンストラクタは、その前に付いているパラメータとして暗黙的にエンクロージング・インスタンスを受け入れるので、エンクロージング・インスタンスに関連する内部クラスは、引数なしのコンストラクタを持つことができません。 したがって、
Externalizable
インタフェース・メカニズムを内部クラスに使用できません。内部クラスを直列化する必要がある場合には、Serializable
インタフェースを実装するようにしてください。 しかし、直列化可能な内部クラスにもいくつかの制限があります。完全な列挙型については、「セクション1.10、"直列化可能インタフェース"」を参照してください。
Externalizableクラスは、オプションで次のメソッドを定義できます。
writeReplace
メソッド - クラスが、ストリームに書き込まれる置換オブジェクトを指定できる(詳細については、「セクション2.5、"writeReplaceメソッド"」を参照してください。)
readResolve
メソッド - クラスが、ストリームから読み込まれたばかりのオブジェクトの置換オブジェクトを指定できる(詳細については、「セクション3.7、"readResolveメソッド"」を参照してください。)
1.12 Enum定数の直列化
enum定数の直列化は、通常の直列化可能または外部化可能オブジェクトとは異なります。 enum定数の直列化された形式を構成するのは、その名前のみです。定数のフィールド値は形式内に存在しません。 enum定数を直列化するために、ObjectOutputStream
はenum定数のname
メソッドで返される値を書き込みます。 enum定数を直列化復元するために、ObjectInputStream
はストリームから定数名を読み取ります。直列化復元された定数は、定数のenum型と受け取った定数名を引数として渡すjava.lang.Enum.valueOf
メソッドを呼び出すことで取得されます。 他の直列化可能または外部化可能オブジェクトと同様に、enum定数は直列化ストリームにその後出現する後方参照のターゲットとして機能できます。
enum定数を直列化するプロセスはカスタマイズできません。enum型で定義されたクラス固有のwriteObject
、readObject
、readObjectNoData
、writeReplace
、readResolve
メソッドは、直列化および直列化復元の間は無視されます。 同様に、serialPersistentFields
またはserialVersionUID
フィールド宣言もすべて無視されます。すべてのenum型は0L
の固定されたserialVersionUID
を持ちます。 送信されるデータの型にはバリエーションがないため、enum型の直列化可能なフィールドおよびデータをドキュメント化する必要はありません。
1.13 レコードの直列化
レコードの直列化は、通常の直列化可能オブジェクトまたは外部化可能オブジェクトとは異なります。 レコード・オブジェクトの直列化された形式は、レコード・コンポーネントから導出された一連の値です。 レコード・オブジェクトのストリーム形式は、ストリーム内の通常のオブジェクトの形式と同じです。 直列化復元中に、指定したストリーム・クラス記述子と同等のローカル・クラスがレコード・クラスである場合、最初にストリーム・フィールドを読み込み、レコード・コンポーネントの値として機能するように再構築します。次に、レコード・オブジェクトを作成するために、コンポーネント値を引数(または、コンポーネント値がストリームにない場合のコンポーネント・タイプのデフォルト値)として使用し、レコードcanonicalコンストラクタを起動します。
他の直列化可能オブジェクトまたは外部化可能オブジェクトと同様に、レコード・オブジェクトも、後で直列化ストリームに出現するバック参照のターゲットとして機能できます。 ただし、コンポーネントの1つによって直接または推移的にレコード・オブジェクトが参照されるグラフ内のサイクルは保持されません。 レコード・コンポーネントは、レコード・コンストラクタの呼出し前に直列化復元されるため、この制限の(詳細は、第1.14項、「"循環参照"」を参照してください。)です。
レコード・オブジェクトの直列化または外部化のプロセスはカスタマイズできません。レコード・クラスで定義されているクラス固有のwriteObject
、readObject
、readObjectNoData
、writeExternal
およびreadExternal
メソッドは、直列化および直列化復元時に無視されます。 ただし、直列化される代替オブジェクトや代理置換を指定する代替オブジェクトは、writeReplace
メソッドとreadResolve
メソッドのそれぞれで指定できます。 serialPersistentFields
フィールド宣言はすべて無視されます。 置換オブジェクトと置換オブジェクトのどちらが使用されているか以外は直列化形式にバリエーションがないため、レコード・クラスの直列化可能フィールドおよびデータのドキュメント化は不要です。 レコード・クラスのserialVersionUID
は、明示的に宣言されないかぎり、0L
です。 serialVersionUID値の一致に関する要件は、レコード・クラスに適用されません。
1.14 循環参照
第1.2項、「"オブジェクト・ストリームへの書込み"」で説明されているように、ハンドルを使用すると、オブジェクト・グラフで発生する循環参照を保持できます。
次の図のみを目的とした、最小限の留めの例です:
class Data implements Serializable {
private static final long serialVersionUID = ...
Object obj;
}
class Carrier implements Serializable {
private static final long serialVersionUID = ...
private final Data d;
public Carrier(Data d) { this.d = d; }
public Data d() { return d; }
}
// create an instance of both Data and Carrier, and a cycle between them
Data d1 = new Data();
Carrier c1 = new Carrier(d1);
d1.obj = c1;
// serialize
ObjectOutputStream oos = new ObjectOutputStream(...);
oos.writeObject(c1);
// deserialize
ObjectInputStream ois = new ObjectInputStream(...);
Carrier c2 = (Carrier) ois.readObject();
直列化復元するとき、オブジェクトc2
のd
フィールドは、Data
のインスタンスを参照します。また、そのobj
フィールドはc2
の同じインスタンスに戻ります。 c2
によって参照されるオブジェクトの識別情報は、c2.d().obj
によって参照されるオブジェクト(つまり、c2 == c2.d().obj
)の識別情報と同じです。
オブジェクトの割当て、c2
およびそのハンドルの割当ては、フィールド値の(第3.1項、「"ObjectInputStreamクラス"、ステップ12を参照)」が再構築される前に行われます。 これにより、直列化復元時に(再帰的にのフィールド値)はc2
のハンドルを参照できます。 このように、通常のオブジェクトの直列化復元ではオブジェクト・グラフのサイクルがサポートされます。
Carrier
がレコード・クラスである場合、次のように考慮します:
record Carrier(Data d) implements Serializable { }
直列化復元するとき、c2
のオブジェクトでは、d
フィールドがData
のインスタンスを参照し、obj
フィールドはnull
(c2
を参照するかわりに)を参照します。 元のオブジェクト・グラフのd.obj
からの循環参照は、直列化復元時に保持されません。
レコード・オブジェクトc2
の割当ておよびそのハンドルの割当ては、フィールド値(将来のレコードのコンポーネント値。第3.1項、「"ObjectInputStreamクラス"、ステップ11」を参照してください。)の再構築後に行われます。 レコード・コンポーネントの値が再構築される前に、レコード・オブジェクトのハンドルが既知のオブジェクトのセットに追加されますが、null
の初期値があります。 このハンドルがレコード・オブジェクトに割り当てられるのは、レコード・オブジェクトが(標準コンストラクタの起動を通じて)で構成された後のみです。 このため、レコード・コンポーネント値の直列化復元中に、ストリーム内のレコード・オブジェクトのハンドルへの参照には、初期null
値が表示されます。 そのため、(またはフィールドの推移的な出現)コンポーネントからレコード・オブジェクトをターゲット設定するサイクルは、直列化復元時に保持されません。
1.15 機密情報の保護
リソースへの制御アクセスを提供するクラスを開発する場合には、機密性の高い情報と機能が保護されるように注意しなければいけません。 直列化復元の際、オブジェクトのprivate状態が復元されます。 たとえば、ファイル記述子には、オペレーティング・システム・リソースへのアクセスを提供するハンドルが含まれています。 状態の復元はストリームから行われるので、ファイル記述子を偽造できるということは、何らかの不法なアクセスが可能だということです。 したがって、直列化の実行時には無難なアプローチを取る必要があり、ストリームにオブジェクトの有効な表現だけが含まれているとは信じないでください。 クラスのセキュリティの低下を回避するために、オブジェクトの機密状態がストリームから復元されないようにする必要があり、またはクラスによって再検証される必要があります。 クラスの機密データを保護するにはいくつかの技法があります。
もっとも簡単な技法は、機密データを含むフィールドをprivate transientとすることです。 transientフィールドは、永続的ではなく、永続性メカニズムによって保存されません。 フィールドをこのようにすると、その状態がストリームに現われず、直列化復元の際にも復元されません。 (privateフィールドの)書き込みや読込みをクラスの外部で行うことはできないので、クラスのtransientフィールドは安全です。
特に機密性の高いクラスは、一切直列化すべきではありません。 これを実現するには、オブジェクトはSerializable
やExternalizable
インタフェースを実装するべきではありません。
クラスによっては、書き込みや読込みは許可するけれども、直列化復元の際に状態を明示的に処理して再検証する方が便利なこともあります。 クラスは、適切な状態だけを保管および復元するwriteObject
およびreadObject
メソッドを実装すべきです。 アクセスを拒否すべき場合には、NotSerializableException
をスローすることで、それ以上のアクセスを防ぎます。