[目次] [前項目] [次項目]

システムアーキテクチャ


トピック:

概要

Java オブジェクトの格納と取り出しの能力は、一過性のものを除くすべてのアプリケーションを作成するために必要なことです。オブジェクトの格納と取り出しで重要なことは、オブジェクトの状態を、それを再構築するのに十分な直列化された形式で表現することです。ストリームに保管されるオブジェクトは、Serializable か Externalizable インタフェースのどちらかをサポートします。Java オブジェクトでは、直列化された形式によって、オブジェクトの内容が保管されていた Java クラスを識別および検証し、その内容を新しいインスタンスに復元できなければなりません。Serializable オブジェクトの場合、ストリームには、そのフィールドを互換性のあるバージョンのクラスに復元できるだけの十分な情報が含まれています。Externalizable オブジェクトの場合、その内容の外部形式については、そのクラスの責任です。

頻繁に格納と取り出しが行われるオブジェクトは、他のオブジェクトを参照します。これらの他のオブジェクトは、それらの間の関係を維持するために、同時に格納と取り出しが行われなければなりません。オブジェクトが格納されると、そのオブジェクトから参照関係にあるすべてのオブジェクトも格納されます。

Java オブジェクトの直列化によるねらいは次のとおりです。

オブジェクトストリームへの書き込み

オブジェクトやプリミティブをストリームに書き込むことは、複雑な処理ではありません。たとえば、次のようにします。

// 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)が、このバイトを受け取るために必要です。次に、OutputStream に書き込む ObjectOutputStream が作成されます。そして、文字列 "Today"と日付オブジェクトがストリームに書き込まれます。一般に、オブジェクトは writeObject メソッドによって書き込まれ、プリミティブはストリームに DataOutput のメソッドによって書き込まれます。

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

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

プリミティブデータ型は、DataOutput インタフェースのメソッド(writeIntwriteFloatwriteUTFなど)でストリームに書き込まれます。個別のバイトと配列バイトは、OutputStream のメソッドで書き込まれます。すべてのプリミティブデータはブロックデータレコードでストリームに書き込まれ、レコードの前にはマーカーと長さが入れられます。レコードにデータを入れることにより、必要ならデータをスキップすることができます。

ObjectOutputStream を拡張すれば、ストリームのクラスに関する情報をカスタマイズしたり、直列化されるオブジェクトを置き換えることができます。詳しくは、annotateClassreplaceObject メソッドの説明を参照してください。

オブジェクトストリームからの読み込み

書き込みと同様、ストリームから読み込むことは、複雑なことではありません。

// 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 メソッドは、ストリームにある次のオブジェクトを直列化復元し、他のオブジェクトへのそれの参照を再帰的に処理(トラバース)して、直列化された完全なオブジェクトグラフを作成します。

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

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

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

オブジェクト直列化によって、1 つまたは複数のプリミティブやオブジェクトが入ったバイトストリームが作成され、消費されます。ストリームに書き込まれたオブジェクトは、そのストリームに表されている他のオブジェクトを順に参照します。オブジェクト直列化によって、ストリームに含まれるオブジェクトをコード化し格納するストリーム形式が、 1 つだけ作成されます。オブジェクト直列化は、Javaクラスに豊富な機能を提供するように設計されています。OLEや OpenDocなど、他のコンテナ形式は、これとは異なるストリームやファイルシステムの表現をもっています。

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

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

Serializable クラスの場合、オブジェクト直列化によって、オブジェクトの各クラスのフィールドが自動的に保管および復元され、フィールドやスーパー型を追加することで発展するクラスが自動的に処理されます。Serializable クラスは、それのどのフィールドが transient(保管もリストもされない)かを宣言し、任意指定の値やオブジェクトの書き込みや読み込みを行うことができます。

Externalizable クラスの場合、オブジェクト直列化では、その外部形式の制御と、スーパー型の状態の保管と復元方法に対する制御がクラスにすべてまかされます。

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 メソッドは、オブジェクトを書き込むために使用します。スローされる例外は、オブジェクトやそのフィールドをアクセスしているとに起こったエラーか、ストレージに書き込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが壊されている可能性があります。詳細については、このインタフェースを実装しているオブジェクトを参照してください。

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 メソッドは、オブジェクトを読み込み、返すために使用します。スローされる例外は、オブジェクトやそのフィールドをアクセスしているときに起こったエラーか、ストレージから読み込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが壊されている可能性があります。詳細については、このインタフェースを実装しているオブジェクトを参照してください。

Serializable インタフェース

オブジェクト直列化を行うと、保管しようとするオブジェクトの Java クラスに関する情報をもつストリームが作成されます。Serializable オブジェクトの場合、そのクラスの異なる(しかし互換性のある)バージョンの実装が存在していても、これらのオブジェクトを復元するのに十分な情報が保持されます。インタフェース Serializable は、Serializable プロトコルを実装するクラスを識別するように定義されます。

package java.io;

public interface Serializable {};
Serializableオブジェクトは、

ObjectOutputStreamObjectInputStream は、それらが作用する Serializable クラスが発展できるように実装および設計されています。この文脈における発展とは、クラスの前のバージョンと互換性がある変更であれば、それらのクラスに対して行うことができるという意味です。互換性のある変更を可能にするメカニズムの詳細については、互換性のある Javaの型展開を参照してください。

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オブジェクトは、

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

センシティブ情報の保護

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

最も簡単な技法は、センシティブなデータを含むフィールドを private transient とすることです。transient と static のフィールドは、直列化も直列化復元もされません。フィールドをこのようにすると、その状態がストリームに現れませんし、直列化復元の際にも復元されません。(private フィールドの)書き込みや読み込みをそのクラスの外で代わりに行うことはできませんので、そのクラスの transient フィールドは安全です。

特にセンシティブなクラスは、一切直列化すべきではありません。これを確実にするには、オブジェクトに Serializable や Externalizable インタフェースを実装しないことです。

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



[目次] [前項目] [次項目]

Copyright (C) 1996, 1997 Sun Microsystems, Inc. All rights reserved.