目次 | 前の項目 | 次の項目 | Java オブジェクト直列化仕様 Version 6.0 |
第 1 章 |
JavaTM オブジェクトの格納と取り出しの能力は、一過性のものを除くすべてのアプリケーションを作成するために必要なことです。直列化された形式でのオブジェクトの格納と取り出しで重要なことは、オブジェクトを再構築できるようにオブジェクトの状態を表現することです。ストリームに保管されるオブジェクトは、Serializable
かExternalizable
インタフェースのどちらかをサポートします。JavaTM オブジェクトでは、直列化された形式によって、オブジェクトの内容が保管されていた JavaTM クラスを識別および検証し、その内容を新しいインスタンスに復元できなければなりません。Serializable オブジェクトの場合、ストリームには、そのフィールドを互換性のあるバージョンのクラスに復元できるだけの十分な情報が含まれています。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」と日付オブジェクトがストリームに書き込まれます。通常、オブジェクトは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」と日付オブジェクトがストリームから読み込まれます。通常、オブジェクトはreadObject
メソッドで読み込まれ、プリミティブはDataInput
のメソッドによってストリームから読み込まれます。
readObject
メソッドは、ストリームにある次のオブジェクトを直列化復元し、readObject
から他のオブジェクトへの参照を再帰的に移動して、直列化する完全なオブジェクトグラフを作成します。プリミティブデータ型は、
DataInput
インタフェースのメソッド (readInt
、readFloat
、readUTF
など) によってストリームから読み込まれます。個別のバイトと配列バイトは、InputStream
のメソッドによって読み込まれます。直列化可能フィールド以外のプリミティブデータはブロックデータレコードから読み込まれます。
ObjectInputStream
を拡張すれば、クラスに関してストリームにある情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳細は、resolveClass
とresolveObject
メソッドの説明を参照してください。
オブジェクト直列化によって、1 つまたは複数のプリミティブやオブジェクトが入ったバイトストリームが作成され、消費されます。ストリームに書き込まれたオブジェクトは、そのストリームに表されている他のオブジェクトを順に参照します。オブジェクトの直列化によって、ストリームに含まれるオブジェクトをコード化し格納するストリーム形式が 1 つだけ作成されます。コンテナとして作用する各オブジェクトには、プリミティブやオブジェクトをそこに格納したり、そこから取り出したりすることができるインタフェースが実装されています。これらのインタフェースは
ObjectOutput
とObjectInput
で、次のことを行います。
ストリームに格納される各オブジェクトは、格納できることを明示的に示さなければなりません。 また、その状態の保管と復元に必要なプロトコルを実装していなければなりません。オブジェクトの直列化では、そのようなプロトコルが 2 つ定義されています。これらのプロトコルによって、コンテナは、オブジェクトの状態を書き込んだり、読み込んだりすることをオブジェクトに依頼することができます。オブジェクトストリームに格納されるためには、各オブジェクトは、
Serializable
かExternalizable
インタフェースを実装しなければなりません。
クラスの直列化可能フィールドは、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 インタフェース」を参照してください。
クラスの直列化状態を文書化して Serializable クラスのほかの実装と相互運用可能にすること、およびクラスの展開を文書化することは重要です。直列化可能フィールドが文書化されていれば、そのフィールドが直列化可能かどうかを最終的に確認することができます。ソースコード内の Serializable クラスの直列化形式を文書化する 1 つの方法は、javadoc の直列化タグ@serial
、@serialField
、および@serialData
を使うことです。
- デフォルトの直列化可能フィールドに対しては、
@serial
タグを javadoc コメント内に配置する必要があります。構文は以下のとおりです。オプションの field-description には、フィールドの意味および設定可能な値を記述します。field-description が複数の列に渡ることも可能です。初期のリリース後にフィールドを追加した場合は、追加したフィールドのバージョンを @since タグを使って示します。@serial
? field-description@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 として宣言した場合、クラス自体によってストリームに書き込まれたデータが直列化状態を定義します。クラスは、ストリームに書き込まれる各データの順番、型、および意味を指定する必要があります。クラスは、以前のバージョンで書き込まれたデータを引き続き読み込めるようにし、以前のバージョンで読み込まれたデータを書き込めるようにするために、自らの展開を処理する必要があります。クラスは、データの保存および復元時には、スーパークラスと協調動作しなければなりません。ストリーム内のスーパークラスのデータ位置を指定する必要があります。
直列化可能クラスの設計者は、そのクラスに保存される情報が持続性に適し、直列化特有の展開および相互運用性の規則に従っていることを保証する必要があります。クラスの展開の詳細については、第 5 章「直列化可能オブジェクトのバージョン管理」を参照してください。
直列化により、ストリーム内の直列化可能フィールドにアクセスするための 2 つの機構が提供されます。
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; }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
メソッドは、オブジェクトの読み込みおよび戻しのために使用します。スローされる例外は、オブジェクトやそのフィールドをアクセスしているときに起こったエラーか、記憶域から読み込んでいるときに起こった例外を表しています。なんらかの例外がスローされた場合、その記憶域が壊されている可能性があります。このような事態が発生した場合は、このインタフェースを実装しているオブジェクトを参照して詳細を確認してください。
オブジェクトの直列化を行うと、保管しようとするオブジェクトの JavaTM クラスに関する情報を持つストリームが作成されます。Serializable オブジェクトの場合、そのクラスの異なる (しかし互換性のある) バージョンの実装が存在していても、これらのオブジェクトを復元するのに十分な情報が保持されます。Serializable
インタフェースは、直列化可能プロトコルを実装するクラスを識別するように定義されます。package java.io; public interface Serializable {};Serializable クラスの要件は以下のとおりです。
serialPersistentFields
メンバーを使ってフィールドを明示的に直列化可能と宣言するか、transient キーワードを使って非直列化可能フィールドを指定する)
Serializable クラスは、オプションで次のメソッドを定義できます。
ObjectOutputStream
およびObjectInputStream
を使用すると、操作対象の直列化可能クラスを展開 (以前のバージョンのクラスとの互換性を持つクラスへの変更が可能) できます。互換性を保つ変更に使用できる機構についての詳細は、「5.5 互換性のある JavaTM の型展開」を参照してください。
注 - ローカルクラスおよび匿名クラスを含む内部クラス (static メンバークラスではない入れ子のクラス) の直列化は、いくつかの理由により、使用しないことを強くお勧めします。非 static コンテキストで宣言された内部クラスには、囲むクラスインスタンスへの暗黙的な非 transient 参照が含まれるので、そのような内部クラスインスタンスを直列化すると、関連する外部クラスインスタンスも直列化されることになります。内部クラスを実装するjavac
(またはその他の JavaTM コンパイラ) によって生成された合成フィールドは、実装に依存するので、コンパイラによって相違が生じることがあります。そのようなフィールドの相違により、デフォルトのserialVersionUID
の値が矛盾するだけでなく、互換性が損なわれる可能性があります。ローカルおよび匿名の内部クラスに割り当てられる名前も実装に依存するので、コンパイラによって相違が生じる可能性があります。内部クラスは、コンパイル時定数フィールド以外の static メンバーを宣言できないので、serialPersistentFields
機構を使って直列化可能フィールドを指定することはできません。さらに、外部インスタンスに関連付けられた内部クラスは、引数なしのコンストラクタ (そのような内部クラスのコンストラクタは、囲むクラスを付加パラメータとして暗黙的に受け入れる) を持たないため、Externalizable
を実装することはできません。ただし、上記の問題はいずれも、static メンバークラスには当てはまりません。
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 クラスは、オプションで次のメソッドを定義できます。
enum 定数の直列化は、通常の直列化可能または外部化可能オブジェクトとは異なります。enum 定数の直列化された形式を構成するのは、その名前だけです。 定数のフィールド値は形式内に存在しません。enum 定数を直列化するには、その enum 定数のname
メソッドで返される値をObjectOutputStream
で書き込みます。enum 定数を直列化復元するには、ObjectInputStream
でストリームから定数名を読み込みます。 次に、受け取った定数名とともに定数の enum 型を引数として渡してjava.lang.Enum.valueOf
メソッドを呼び出し、直列化復元された定数を取得します。ほかの直列化可能または外部化可能オブジェクト同様に、enum 定数は、以後直列化ストリームに出現する後方参照の対象として機能できます。enum 定数を直列化するプロセスをカスタマイズすることはできません。enum 型で定義された、クラス固有の
writeObject
、readObject
、readObjectNoData
、writeReplace
、およびreadResolve
メソッドのすべては、直列化および直列化復元の間は無視されます。同様に、serialPersistentFields
またはserialVersionUID
フィールド宣言もすべて無視されます。 すべての enum 型は0L
で固定されたserialVersionUID
を持ちます。送信されるデータの型には種類がないため、enum 型の直列化可能なフィールドおよびデータを文書化する必要はありません。
リソースのコントロールアクセスを行うクラスを開発する場合には、機密性の高い情報と機能が保護されるように注意しなければなりません。直列化復元の際、オブジェクトの private 状態が復元されます。たとえば、ファイル記述子には、オペレーティングシステムにアクセスできるハンドルが含まれています。状態の復元はストリームから行われるので、ファイル記述子を偽造できるということは、なんらかの不法なアクセスが可能だということです。したがって、直列化の実行時には安全なアプローチを取るべきであり、ストリームにオブジェクトの有効な表現だけが含まれているとは信じないことです。クラスを正しく保つには、オブジェクトの機密性の高い状態を、ストリームから復元しないようにするか、そのクラスによって再び検証するようにする必要があります。クラスの機密性の高いデータを保護するにはいくつかの技法があります。もっとも簡単な技法は、機密性の高いデータを含むフィールドを private transient とすることです。transient フィールドは、持続的ではなく、持続性機構によって保存されません。フィールドをこのようにすると、その状態がストリームに現われず、直列化復元の際にも復元されません。(private フィールドの) 書き込みや読み込みをそのクラスの外部で代わりに行うことはできないので、そのクラスの transient フィールドは安全です。
特に機密性の高いクラスは、一切直列化すべきではありません。これを確実にするには、オブジェクトに
Serializable
やExternalizable
インタフェースを実装しないことです。クラスによっては、書き込みや読み込みを許し、直列化復元の際に状態を特に処理して再検証する方が便利なこともあります。クラスには
writeObject
とreadObject
のメソッドを実装して、適切な状態だけを保管および復元すべきです。アクセスが拒否される場合には、NotSerializableException
がスローされ、それ以上のアクセスは行われません。