直列化のカスタマイズ

Revised: Mar./23rd/2002

直列化可能なオブジェクト

オブジェクトのストリームで実際に読み書きされるのは、当該オブジェクトを再構築するのに必要な情報です。デフォルトで書き込まれるのは次の情報です:

フィールドで他のオブジェクトが参照されていれば、これも書き込まれます。

このストリームで直列化可能なオブジェクトは、デフォルトでは次のような条件のクラスから作られたものに限られます:

スーパークラスが直列化可能でない場合は、復元するときに引数のないコンストラクタが呼ばれて再構築されます。したがって、このスーパークラスが引数のないコンストラクタを実装していない場合は、サブクラスも直列化できません。

引数のないコンストラクタで構築されたスーパークラスを適切な状態に更新する為には、サブクラスで writeObject()/readObject() をオーバーライドして、直列化プロセスをカスタマイズする必要があります。

フィールドで直列化可能でないオブジェクトを参照している場合は、 transient 修飾子で直列化されないように明示しておく必要があります。この場合は復元されるときに 0/null で初期化されますので、適切な状態に更新する為には writeObject()/readObject() をオーバーライドして、直列化プロセスをカスタマイズする必要があります。

直列化のカスタマイズ

先に紹介した直列化/直列化復元は、デフォルトの挙動です。ここにオブジェクトを持続させる為に不必要な情報が含まれる場合には、直列化の挙動を明示することでより高速に処理することも出来ます。また、直列化不可能なオブジェクトの参照を再構築する必要がある場合にも、直列化の挙動をカスタマイズする必要があります。

直列化プロセスをカスタマイズする為には、直列化するクラスで次の二つのメソッドをオーバーライドします:

private void writeObject(ObjectOutputStream s) throws IOException
保存するフィールドを明示し、必要があれば追加情報を付加する
private void readObject(ObjectInputStream s) throws IOException
対応する writeObject() メソッドで書き込まれた情報を読み込み、必要があれば復元されたオブジェクトの情報を更新する

何れの場合も、先に説明したデフォルトの直列化プロセスを実装しておくために、出力では defaultWriteObject()、入力は defaultReadObject() を呼び出す必要があります。

// 直列化のカスタマイズ
private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    // customized serialization code
}

// 直列化復元のカスタマイズ
private void readObject(ObjectInputStream s) throws IOException  {
    s.defaultReadObject();
    // customized deserialization code
    ...
    // followed by code to update the object, if necessary
}

readObject() メソッドは対応する writeObject() メソッドと同じ順番で読み込む必要があります。また、必要があればオブジェクトの状態を更新するコードを続けることも出来ます。例えば、 transient 宣言したフィールドを読み取り時にセットし直す場合、直列化不可能なオブジェクトを再構築する場合に使います。

サンプル

直列化可能でないクラス Oya と、直列化可能なサブクラス Ko を定義します。

直列化可能でないスーパークラス

class Oya {
	private int x, y;

	// 引数のないコンストラクタ
	public Oya() {
		x = 0;
		y = 0;
	}

	// 引数のあるコンストラクタ
	public Oya(int x, int y) {
		this.x = x;
		this.y = y;
	}

	// x のセッター
	public void setX(int x) {
		this.x = x;
	}
	// y のセッター
	public void setY(int y) {
		this.y = y;
	}
	// x のゲッター
	public int getX() {
		return x;
	}
	// y のゲッター
	public int getY() {
		return y;
	}
}

ここでは、コンストラクタが二つ定義されています。コンストラクタを明示的に定義していない場合は、引数のないコンストラクタが自動的に実装されます。一つでも明示的にコンストラクタを定義すると、デフォルトで実装されていた引数のないコンストラクタは消えます。引数のないコンストラクタが必要な場合は、明示的に引数のないコンストラクタを実装する必要があります。

直列化可能なサブクラス

直列化可能でない Oya クラスを継承して、直列化可能なサブクラス Ko を作ります。 Oya クラスが直列化可能ではないのでカスタマイズして親クラスの直列化の面倒も見なければなりません。

import java.io.*;

class Ko extends Oya implements Serializable {
	private String name;

	// コンストラクタ
	public Ko(int i, int j, String str) {
		this.name = str;
		super.setX(i);
		super.setY(j);
	}

	// name のゲッター
	public String getName() {
		return name;
	}

	// 直列化のカスタマイズ
	private void writeObject(ObjectOutputStream out)
		throws IOException {

		// デフォルトの直列化プロセス
		out.defaultWriteObject();

		// 追加の直列化処理
		out.writeInt(super.getX());
		out.writeInt(super.getY());
	}

	// 直列化復元のカスタマイズ
	private void readObject(ObjectInputStream in)
		throws IOException, ClassNotFoundException {

		// デフォルトの直列化復元のプロセス
		in.defaultReadObject();

		// 追加の復元処理
		int x = in.readInt();
		int y = in.readInt();
		super.setX(x);
		super.setY(y);
	}
}

それでは、 Ko クラスをインスタンス化して、オブジェクトをファイルに対して入出力してみます。

オブジェクトの出力ストリーム

SerializeTest.java:

import java.io.*;

class SerializeTest {
	public static void main(String[] args)
		throws IOException {

		Ko obj = new Ko(10, 20, "子供");
	// 直列化するオブジェクトのチェック
		System.out.println("----直列化!----");
		System.out.println(obj.getName());
		System.out.println(obj.getX());
		System.out.println(obj.getY());

	// オブジェクトをファイルに書き込み
		FileOutputStream outFile = new FileOutputStream("tmp");
		ObjectOutput out = new ObjectOutputStream(outFile);

		out.writeObject(obj);
		out.flush();

		out.close();
		outFile.close();
	}
}

オブジェクトの入力ストリーム

ファイルに書き込みましたから、あとで復元できます。復元する処理を作ります。

DeserializeTest.java:

import java.io.*;

class DeserializeTest {
	public static void main(String[] args)
		throws IOException, ClassNotFoundException {

	// ファイルからオブジェクトを読み込んで復元
		FileInputStream inFile = new FileInputStream("tmp");
		ObjectInputStream in = new ObjectInputStream(inFile);

		Ko restore = (Ko)in.readObject();

		in.close();
		inFile.close();

	// 復元されたオブジェクトのチェック
		System.out.println("-----復元!-----");
		System.out.println(restore.getName());
		System.out.println(restore.getX());
		System.out.println(restore.getY());
	}
}

実行例

C:\IO>javac Ko.java

C:\IO>javac SerializeTest.java

C:\IO>javac DeserializeTest.java

C:\IO>java SerializeTest
----直列化!----
子供
10
20

C:\IO>java DeserializeTest
-----復元!-----
子供
10
20

C:\IO>

SerializeTest でインスタンス化した Ko 型オブジェクトがファイルに保存され、別のアプリケーションである DeserializeTest で復元されています。オブジェクトを持続的 (persistent) にアクセスできるようになっています。

Externalizable インタフェース

デフォルトの直列化よりも、カスタマイズする部分の方が多い場合には、 Externalizable インタフェースを実装して、 writeExternal()/readExternal() を用いた方が良いでしょう。オブジェクトを再構築するための情報が限られておりデフォルトの機構では冗長な場合や、 JavaVM のデフォルトフォーマットではなく、直列化するクラスでフォーマットを明示したい場合は、こちらで実装した方が良いでしょう。

この場合は、当該オブジェクトのクラス型は自動的に入出力されますので、必要なフィールドの値だけ実装するようにすればよいわけです。



Copyright © 2002 SUGAI, Manabu. All Rights Reserved.