補足(いいわけ?)001


「7.2 列挙型を作る」について(2002/6/30)

「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回続けて出力されます。 列挙が==で比較できていることがわかります。


「7.4.デバッグライト」について(2002/11/30)

デバッグライトでクラス名を取得するため、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からは、ソースファイル名や行番号も取得できます。 便利になったものです。


「補足(いいわけ?)」の目次に戻る | ひとつ前のページ | ひとつ後のページ | 「Java謎+落とし穴徹底解明」のページに戻る | トップページに戻る