2006/11/04

[Java]Singleton and Serialization don't mix

SingletonパターンやTypeSafeEnumパターンは、シリアライズとの相性が非常に悪い。
この手のパターンはインスタンスの生成を制限することで成り立っているからだ。 Singletonはインスタンスを一つに制限するパターン。

public class Singleton {     private Singleton() {}     private static final INSTANCE = new Singleton();     public Singleton getInstance() {         return INSTANCE;     } }

インスタンスの取得はこのgetInstance()メソッドを使用する。
TypeSafeEnumパターンは、定数に関する問題を解決しようとするもので、比較をインスタンス比較で行うことにより値を意識せずに実装しようというもの。

public class Color {     private String name;     public static final RED = new Color("red");     public static final BLUE = new Color("blue");     public static final GREEN = new Color("green");     private Color(String name) {         this.name = name;     }     public String toString() {         return this.name;     } }

このクラスを使用するときは、 if (color == Color.RED) {のようにして、インスタンス比較を行う。
※上記のソースは、機能の要素だけを書いているので、シリアライズ以外にも問題点はいろいろある

これらのクラスがSerializableだとどうなるかというと、デシリアライズの結果VM上に上記の定数などで管理しているインスタンスとは異なるインスタンスが生成される。Singletonはsingleじゃなくなるし、Colorクラスは赤と赤なのに等しくなくなる。TypeSafeEnumは値を表すクラスなので、DTOのフィールドとしてシリアライズされる可能性はそれなりに高い。
Singletonをシリアライズするようなな作りは微妙だ。設定ファイルの参照やログ出力クラスがほとんどだし、他のVMに送信して使うような内容じゃないだろう。
#そういう場面に出会ったことがあるけど、完全に愚痴なので別に書こう。

インスタンスの数を解決するには各クラスに、readObjectを実装する。このメソッドはserialVersionUIDと同じく、Javaのシリアライズ機構が利用する。このメソッドは名前の通り読み込み時に参照の再解決を行う。返却してやるインスタンスを挿げ替えることで、クラスの利用者にはそのVMで使用すべきインスタンスを渡すことができる。

public class Singleton implements Serializable {     private Singleton() {}     private static final INSTANCE = new Singleton();     public Singleton getInstance() {         return INSTANCE;     }     private Object readResolve() {         return INSTANCE;     } } public class Color implements Serializable {     private static Map colors = new HashMap(); // インスタンス管理用     private String name;     public static final RED = new Color("red");     public static final BLUE = new Color("blue");     public static final GREEN = new Color("green");     private Color(String name) {         this.name = name;         colors.put(name, this); // 名前で登録しておく     }     public String toString() {         return this.name;     }     private Object readResolve() {         return colors.get(this.name); // 同じ名前のインスタンスを返却     } }

これで、デシリアライズを行った後にはVMで使用していたインスタンスが返却されるようになる。TypeSafeEnumでは、値に該当するインスタンスがなかったときのチェックなども実装した方がいい。

ラベル: