補足(いいわけ?)001
「7.2 列挙型を作る」において、p.346に以下のような記述があります。
何がまずいのかというと、 直列化(シリアライズ)したデータをデシリアライズすると 新しいインスタンスが生成されてしまう ことです。
この記述自体は嘘ではありません。しかし、JDK1.2以降のJavaでは、 これを回避する手段が提供されています。
readResolve()メソッドというのがそれです。 その説明は、以下のページにあります。
http://java.sun.com/products/jdk/1.2/ja/docs/ja/guide/serialization/spec/input.doc6.html
つまり、デシリアライズの際、readResolve()メソッドが定義されていると、 デシリアライズで生成されたオブジェクトが、 readResolve()がreturnするオブジェクトに置きかえられる、ということです。 よって、ここで列挙のオブジェクトを検索してreturnすれば、 新しいインスタンスが生成されてしまうことはなく(厳密に言うと、 一瞬作られてすぐに捨てられる)、 列挙同士を==で比較できます。
サンプルのLabelAlignmentクラスを、以下に載せておきます。
import java.io.*;
class LabelAlignment implements Serializable {
private int value;
// 勝手にインスタンスを作れないように、
// コンストラクタをprivateにする。
private LabelAlignment(int value) {
this.value = value;
}
public static final LabelAlignment LEFT = new LabelAlignment(0);
public static final LabelAlignment CENTER = new LabelAlignment(1);
public static final LabelAlignment RIGHT = new LabelAlignment(2);
Object readResolve() throws ObjectStreamException {
switch (this.value) {
case 0:
return LabelAlignment.LEFT;
case 1:
return LabelAlignment.CENTER;
case 2:
return LabelAlignment.RIGHT;
default:
// ホントはよくない
return null;
}
}
}
default節に「ホントはよくない」とありますが、 これは本来、ObjectStreamException(のサブクラス)をthrowすべきでしょう。
テストプログラムはこちら。
import java.io.*;
class Test {
public static void main(String[] args) {
LabelAlignment[] array = {
LabelAlignment.LEFT,
LabelAlignment.CENTER,
LabelAlignment.RIGHT
};
LabelAlignment[] array2 = null;
try {
FileOutputStream fo = new FileOutputStream("hoge");
ObjectOutputStream oo = new ObjectOutputStream(fo);
oo.writeObject(array);
oo.close();
FileInputStream fi = new FileInputStream("hoge");
ObjectInputStream oi = new ObjectInputStream(fi);
array2 = (LabelAlignment[])oi.readObject();
oi.close();
} catch (Exception e) {
System.out.println(e);
System.exit();
}
for (int i = 0; i < array.length; i++) {
if (array[i] == array2[i]) {
System.out.println("OK!");
}
}
}
}
実行すると、「OK!」が3回続けて出力されます。 列挙が==で比較できていることがわかります。
デバッグライトでクラス名を取得するため、SecurityManagerの getStackTrace()を使う方法を紹介していますが、JDK1.4からは、 例外オブジェクトそのものからクラス名・メソッド名を取得する方法が 提供されています。
ThrowableクラスのgetStackTraceElement()メソッドにより、 StackTraceElementの配列が取得できます。 これがスタックトレースを表現しています。 この配列の[1]から、呼び出し元の情報が取得できます。
これを使用すると、List7.2のDebugクラスは、 以下のように書きかえることが出来ます。 ついでに、メソッド名を一緒に表示するように改善しました。
import java.util.*;
public final class Debug {
public static void write(String str) {
Throwable t = new Throwable();
// [0]はこのメソッドなので、[1]が呼び出し元。
StackTraceElement element = t.getStackTrace()[1];
// 完全限定クラス名を取得
String className = element.getClassName();
// パッケージ名まで出ると鬱陶しいと思うので、
// パッケージ名を除くクラス名を取得する。
StringTokenizer tokenizer = new StringTokenizer(className, ".");
String lastClassName = null;
while (tokenizer.hasMoreTokens()) {
lastClassName = tokenizer.nextToken();
}
// クラス名とメソッド名を併せて表示する。
System.err.println("[" +lastClassName
+ "/" + element.getMethodName()
+ "]" + str);
}
}
StackTraceElementからは、ソースファイル名や行番号も取得できます。 便利になったものです。