補足(いいわけ?)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からは、ソースファイル名や行番号も取得できます。 便利になったものです。