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

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






第 1 章

トピック:


1.1 概要

JavaTM オブジェクトの格納と取り出しの能力は、一過性のものを除くすべてのアプリケーションを作成するために必要なことです。直列化された形式でのオブジェクトの格納と取り出しで重要なことは、オブジェクトを再構築できるようにオブジェクトの状態を表現することです。ストリームに保管されるオブジェクトは、SerializableExternalizable インタフェースのどちらかをサポートします。JavaTM オブジェクトでは、直列化された形式によって、オブジェクトの内容が保管されていた JavaTM クラスを識別および検証し、その内容を新しいインスタンスに復元できなければなりません。Serializable オブジェクトの場合、ストリームには、そのフィールドを互換性のあるバージョンのクラスに復元できるだけの十分な情報が含まれています。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」と日付オブジェクトがストリームに書き込まれます。通常、オブジェクトは writeObject メソッドによって書き込まれ、プリミティブはストリームに DataOutput のメソッドによって書き込まれます。

writeObject メソッド (「2.3 writeObject メソッド」を参照) は、指定されたオブジェクトを直列化し、オブジェクトグラフにある他のオブジェクトへのそれの参照を再帰的に処理 (トラバース) して、そのグラフを完全に直列化した表現を作成します。ストリーム内でオブジェクトに対する初めての参照があると、そのオブジェクトが直列化または外部化され、そのオブジェクトのハンドルが割り当てられます。そのオブジェクトに対するそれ以後の参照は、そのハンドルとしてコード化されます。オブジェクトハンドルを使用すれば、オブジェクトにおいて当然起こる共用参照や循環参照が保存されます。オブジェクトのそれ以後の参照ではそのハンドルだけを使用するため、非常に簡潔な表現が可能になります。

配列、enum 定数、および ClassObjectStreamClassString 型のオブジェクトには、特別の処理が必要です。他のオブジェクトをストリームに保管したり、そこから取り出したりするには、それらのオブジェクトに SerializableExternalizable インタフェースが実装されていなければなりません。

プリミティブデータ型は、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」と日付オブジェクトがストリームから読み込まれます。通常、オブジェクトは readObject メソッドで読み込まれ、プリミティブは DataInput のメソッドによってストリームから読み込まれます。

readObject メソッドは、ストリームにある次のオブジェクトを直列化復元し、readObject から他のオブジェクトへの参照を再帰的に移動して、直列化する完全なオブジェクトグラフを作成します。

プリミティブデータ型は、DataInput インタフェースのメソッド (readIntreadFloatreadUTF など) によってストリームから読み込まれます。個別のバイトと配列バイトは、InputStream のメソッドによって読み込まれます。直列化可能フィールド以外のプリミティブデータはブロックデータレコードから読み込まれます。

ObjectInputStream を拡張すれば、クラスに関してストリームにある情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳細は、resolveClassresolveObject メソッドの説明を参照してください。


1.4 コンテナとしてのオブジェクトストリーム

オブジェクト直列化によって、1 つまたは複数のプリミティブやオブジェクトが入ったバイトストリームが作成され、消費されます。ストリームに書き込まれたオブジェクトは、そのストリームに表されている他のオブジェクトを順に参照します。オブジェクトの直列化によって、ストリームに含まれるオブジェクトをコード化し格納するストリーム形式が 1 つだけ作成されます。

コンテナとして作用する各オブジェクトには、プリミティブやオブジェクトをそこに格納したり、そこから取り出したりすることができるインタフェースが実装されています。これらのインタフェースは ObjectOutputObjectInput で、次のことを行います。

ストリームに格納される各オブジェクトは、格納できることを明示的に示さなければなりません。 また、その状態の保管と復元に必要なプロトコルを実装していなければなりません。オブジェクトの直列化では、そのようなプロトコルが 2 つ定義されています。これらのプロトコルによって、コンテナは、オブジェクトの状態を書き込んだり、読み込んだりすることをオブジェクトに依頼することができます。

オブジェクトストリームに格納されるためには、各オブジェクトは、SerializableExternalizable インタフェースを実装しなければなりません。


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 クラスの writeObject および readObject メソッドは、クラスの最新の実装を 「1.7 クラスの直列化可能フィールドへのアクセス」に記述されているインタフェースを使用してクラスの直列化可能フィールドにマッピングできます。このため、Serializable クラスのフィールドは、後のリリースで変更できます。 ただし、フィールドが直列化可能フィールドへのマッピングを維持していて、直列化可能フィールドが異なるリリース間でも互換性がある場合に限ります。


注 - この機構を使用して内部クラスに対して直列化可能フィールドを指定する場合には、制限があります。内部クラスは、定数または定数からなる式に初期化された、final static フィールドのみ含むことができます。したがって、内部クラスに serialPersistentFields を設定することはできません。ただし、static メンバークラスには設定できます。内部クラスのインスタンスの直列化に関するその他の制限事項については、「1.10 Serializable インタフェース」を参照してください。


1.6 クラスの直列化可能なフィールドおよびデータの文書化

クラスの直列化状態を文書化して Serializable クラスのほかの実装と相互運用可能にすること、およびクラスの展開を文書化することは重要です。直列化可能フィールドが文書化されていれば、そのフィールドが直列化可能かどうかを最終的に確認することができます。ソースコード内の Serializable クラスの直列化形式を文書化する 1 つの方法は、javadoc の直列化タグ @serial@serialField、および @serialData を使うことです。

javadoc アプリケーションは、javadoc の直列化タグを認識し、各 Serializable クラスおよび Externalizable クラスの仕様を生成します。これらのタグを使った例については、「C.1 java.io.File 代替実装の例」を参照してください。

クラスを Serializable として宣言した場合、オブジェクトの直列化可能状態は、直列化可能フィールド (名前と型による) に加え、任意指定のデータによって定義されます。任意指定のデータは、Serializable クラスの writeObject メソッドによってのみ書き込まれます。任意指定のデータは、Serializable クラスの readObject メソッドによって読み込まれるか、または直列化では読み込まれずにスキップされます。

クラスを Externalizable として宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるようにし、以前のバージョンで読み込まれたデータを書き込めるようにするために、自らの展開を処理する必要があります。クラスは、データの保存および復元時には、スーパークラスと協調動作しなければなりません。ストリーム内のスーパークラスのデータ位置を指定する必要があります。

直列化可能クラスの設計者は、そのクラスに保存される情報が持続性に適し、直列化特有の展開および相互運用性の規則に従っていることを保証する必要があります。クラスの展開の詳細については、第 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;
}
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 オブジェクトの場合、そのクラスの異なる (しかし互換性のある) バージョンの実装が存在していても、これらのオブジェクトを復元するのに十分な情報が保持されます。Serializable インタフェースは、直列化可能プロトコルを実装するクラスを識別するように定義されます。

package java.io;

public interface Serializable {};
Serializable クラスの要件は以下のとおりです。

(serialPersistentFields メンバーを使ってフィールドを明示的に直列化可能と宣言するか、transient キーワードを使って非直列化可能フィールドを指定する)

Serializable クラスは、オプションで次のメソッドを定義できます。

(詳細は「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 オブジェクトは、その状態を保存するためにそのスーパータイプと明示的に調整しなければならない)


注 - writeExternalreadExternal のメソッドは public であるため、クライアントが、オブジェクトのメソッドとフィールドを使わずにオブジェクトの情報を書き込んだり、読み込んだりできる危険性があります。これらのメソッドを使うのは、オブジェクトで表す情報が機密に属するものでないときや、書き込みや読み込みがあっても機密保護を妨害する可能性のない場合だけにしなければなりません。

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

(詳細は「2.5 writeReplace メソッド」を参照)

(詳細は「3.7 readResolve メソッド」を参照)


1.12 Enum 定数の直列化

enum 定数の直列化は、通常の直列化可能または外部化可能オブジェクトとは異なります。enum 定数の直列化された形式を構成するのは、その名前だけです。 定数のフィールド値は形式内に存在しません。enum 定数を直列化するには、その enum 定数のname メソッドで返される値を ObjectOutputStream で書き込みます。enum 定数を直列化復元するには、ObjectInputStream でストリームから定数名を読み込みます。 次に、受け取った定数名とともに定数の enum 型を引数として渡して java.lang.Enum.valueOf メソッドを呼び出し、直列化復元された定数を取得します。ほかの直列化可能または外部化可能オブジェクト同様に、enum 定数は、以後直列化ストリームに出現する後方参照の対象として機能できます。

enum 定数を直列化するプロセスをカスタマイズすることはできません。enum 型で定義された、クラス固有の writeObjectreadObjectreadObjectNoDatawriteReplace、および readResolve メソッドのすべては、直列化および直列化復元の間は無視されます。同様に、serialPersistentFields または serialVersionUID フィールド宣言もすべて無視されます。 すべての enum 型は 0L で固定された serialVersionUID を持ちます。送信されるデータの型には種類がないため、enum 型の直列化可能なフィールドおよびデータを文書化する必要はありません。


1.13 機密情報の保護

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

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

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

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



目次 | 前の項目 | 次の項目
Copyright © 2005 Sun Microsystems, Inc. All Rights Reserved.