頻繁に格納と取り出しが行われるオブジェクトは、他のオブジェクトを参照します。これらの他のオブジェクトは、それらの間の関係を維持するために、同時に格納と取り出しが行われなければなりません。オブジェクトが格納されると、そのオブジェクトから参照関係にあるすべてのオブジェクトも格納されます。
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 インタフェースのメソッド(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 メソッドは、ストリームにある次のオブジェクトを直列化復元し、他のオブジェクトへのそれの参照を再帰的に処理(トラバース)して、直列化された完全なオブジェクトグラフを作成します。
プリミティブデータ型は、DataOutput インタフェースのメソッド(readInt、readFloat、readUTFなど)によってストリームから読み込まれます。個別のバイトと配列バイトは、InputStream のメソッドによって読み込まれます。すべてのプリミティブデータはブロックデータレコードから読み込まれます。
ObjectInputStream を拡張すれば、クラスに関してストリームにある情報を利用したり、直列化復元されたオブジェクトを置き換えることができます。詳しくは、resolveClass と resolveObject メソッドの説明を参照してください。
コンテナとして作用する各オブジェクトには、プリミティブやオブジェクトをそこに格納したり、そこから取り出したりすることができるインタフェースが実装されています。これらのインタフェースは ObjectOutput と ObjectInput で、次のことを行います。
Serializable クラスの場合、オブジェクト直列化によって、オブジェクトの各クラスのフィールドが自動的に保管および復元され、フィールドやスーパー型を追加することで発展するクラスが自動的に処理されます。Serializable クラスは、それのどのフィールドが transient(保管もリストもされない)かを宣言し、任意指定の値やオブジェクトの書き込みや読み込みを行うことができます。
Externalizable クラスの場合、オブジェクト直列化では、その外部形式の制御と、スーパー型の状態の保管と復元方法に対する制御がクラスにすべてまかされます。
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 メソッドは、オブジェクトを読み込み、返すために使用します。スローされる例外は、オブジェクトやそのフィールドをアクセスしているときに起こったエラーか、ストレージから読み込んでいるときに起こった例外を表しています。何らかの例外がスローされた場合、そのストレージが壊されている可能性があります。詳細については、このインタフェースを実装しているオブジェクトを参照してください。
package java.io;
public interface Serializable {};
Serializableオブジェクトは、
java.io.Serializable インタフェースを実装しなければならない。
writeObject メソッドを実装することによって、保管する情報を制御したり、情報をストリームに追加したりすることができる。
readObject メソッドを実装することによって、対応する writeObjectメソッドで書き込まれた情報を読み込んだり、オブジェクトが復元された後にその状態を更新したりすることができる。
ObjectOutputStream と ObjectInputStream は、それらが作用する Serializable クラスが発展できるように実装および設計されています。この文脈における発展とは、クラスの前のバージョンと互換性がある変更であれば、それらのクラスに対して行うことができるという意味です。互換性のある変更を可能にするメカニズムの詳細については、互換性のある Javaの型展開を参照してください。
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 であるため、クライアントが、オブジェクトのメソッドとフィールドを使わずにオブジェクトの情報を書き込んだり、読み込んだりできるおそれがあります。これらのメソッドを使うのは、オブジェクトで表す情報がセンシティブでないときや、書き込みや読み込みがあっても機密保護のリスクとはならない場合だけにしなければなりません。
最も簡単な技法は、センシティブなデータを含むフィールドを private transient とすることです。transient と static のフィールドは、直列化も直列化復元もされません。フィールドをこのようにすると、その状態がストリームに現れませんし、直列化復元の際にも復元されません。(private フィールドの)書き込みや読み込みをそのクラスの外で代わりに行うことはできませんので、そのクラスの transient フィールドは安全です。
特にセンシティブなクラスは、一切直列化すべきではありません。これを確実にするには、オブジェクトに Serializable や Externalizable インタフェースを実装しないことです。
クラスによっては、書き込みや読み込みを許し、直列化復元の際に状態を特に処理して再検証する方が便利なこともあります。クラスには writeObject と readObject のメソッドを実装して、適切な状態だけを保管および復元すべきです。アクセスが拒否される場合には、NotSerializableException がスローされ、それ以上のアクセスは行われません。