目次|| Javaオブジェクト直列化仕様
Version 6.0

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





第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クラスの要件は以下のとおりです。
(serialPersistentFieldsメンバーを使ってフィールドを明示的に直列化可能と宣言するか、transientキーワードを使って非直列化可能フィールドを指定する)
このクラスは、オプションで次のメソッドを定義できます。
(詳細は、セクション2.5「writeReplaceメソッド」を参照してください。)
(詳細は、セクション3.7「readResolveメソッド」を参照してください。)
ObjectOutputStreamおよびObjectInputStreamを使用すると、操作対象の直列化可能クラスを展開できます(以前のバージョンのクラスとの互換性を持つクラスへの変更が可能)。互換性を保つ変更に使用できるメカニズムについての詳細は、セクション5.5「互換性のあるJavaTMの型展開」を参照してください。
注意 - ローカル・クラスおよび匿名クラスを含む内部クラス(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オブジェクトは、その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)
(Externalizableオブジェクトは、その状態を保存するためにそのスーパー・タイプと明示的に連携しなければいけない。)

注 - writeExternalおよびreadExternalメソッドはpublicであり、クライアントが、メソッドとフィールドを使わずにオブジェクト内で情報を書き込んだり読み込んだりできる危険性があります。これらのメソッドを使うのは、オブジェクトが保持する情報が機密でないとき、またはそれを公開してもセキュリティ・リスクが発生しないときだけにしなければいけません。

注意 - 内包するインスタンスに関連付けられた内部クラスは、引数なしのコンストラクタを持つことができません。そのようなクラスのコンストラクタは、内包するインスタンスを付加パラメータとして暗黙的に受け入れるためです。したがって、Externalizableインタフェース・メカニズムを内部クラスに使用できません。内部クラスを直列化する必要がある場合には、Serializableインタフェースを実装するようにしてください。ただし、直列化可能な内部クラスの場合でも、いくつかの制限事項があります。その詳細は、1.10項「Serializableインタフェース」を参照してください。
Externalizableクラスは、オプションで次のメソッドを定義できます。
(詳細は、セクション2.5「writeReplaceメソッド」を参照してください。)
(詳細は、セクション3.7「readResolveメソッド」を参照してください。)

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 機密情報の保護

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

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

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

クラスによっては、書き込みや読込みは許可するけれども、直列化復元の際に状態を明示的に処理して再検証する方が便利なこともあります。クラスは、適切な状態だけを保管および復元するwriteObjectおよびreadObjectメソッドを実装すべきです。アクセスを拒否すべき場合には、NotSerializableExceptionをスローすることで、それ以上のアクセスを防ぎます。



目次||
Copyright © 2005, 2010, Oracle and/or its affiliates. All rights reserved.