インタビューでインタビュアーは私に次の質問をしました:シングルトンオブジェクトをシリアル化することは可能ですか?はいと言いましたが、どのシナリオでシングルトンをシリアル化する必要がありますか?
また、オブジェクトをシリアル化できないクラスを設計することはできますか?
質問はおそらく、「シングルトンパターンクラス[〜#〜] c [〜#〜]シングルトンパターンを壊さない方法で?」
答えは基本的にはいです:
import Java.io.IOException;
import Java.io.ObjectInputStream;
import Java.io.ObjectStreamException;
import Java.io.Serializable;
public class AppState implements Serializable
{
private static AppState s_instance = null;
public static synchronized AppState getInstance() {
if (s_instance == null) {
s_instance = new AppState();
}
return s_instance;
}
private AppState() {
// initialize
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
synchronized (AppState.class) {
if (s_instance == null) {
// re-initialize if needed
s_instance = this; // only if everything succeeds
}
}
}
// this function must not be called other than by the deserialization runtime
private Object readResolve() throws ObjectStreamException {
assert(s_instance != null);
return s_instance;
}
public static void main(String[] args) throws Throwable {
assert(getInstance() == getInstance());
Java.io.ByteArrayOutputStream baos = new Java.io.ByteArrayOutputStream();
Java.io.ObjectOutputStream oos = new Java.io.ObjectOutputStream(baos);
oos.writeObject(getInstance());
oos.close();
Java.io.InputStream is = new Java.io.ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
AppState s = (AppState)ois.readObject();
assert(s == getInstance());
}
}
ただし、このコードを使用すると、AppState
の複数のインスタンスが存在する可能性がありますです。ただし、参照されるのは1つだけです。その他は、逆シリアル化ランタイムによってのみ作成されたガベージコレクションの対象となるため、実用的な目的では存在しません。
他の2つの質問に対する答え(どのシナリオでシングルトンをシリアル化する必要がありますか?オブジェクトをシリアル化できないクラスを設計することは可能ですか?)については、 @Michael Borgwardtの回答 を参照してください。
どのシナリオでシングルトンをシリアル化する必要があります。
実行時間の長いアプリがあり、それをシャットダウンして、後でシャットダウンされたポイントで続行できるようにしたいと想像してください(たとえば、ハードウェアのメンテナンスを行うため)。アプリがステートフルなシングルトンを使用する場合、シグルトンの状態を保存および復元できる必要があります。これは、シリアル化することで最も簡単に実行できます。
また、オブジェクトをシリアル化できないクラスを設計することは可能です。
実際、非常に単純です:Serializable
を実装せず、クラスをfinal
シングルトンオブジェクトをシリアル化することは可能ですか?
シングルトンの実装方法によって異なります。シングルトンが1つの要素を持つ列挙型として実装されている場合は、デフォルトで次のようになります。
_// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
_
シングルトンが単一要素の列挙型を使用して実装されていないが、静的ファクトリーメソッドを使用している場合(バリアントはpublic static finalフィールドを使用することです):
_// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}
_
次に、_implements Serializable
_を追加してserializable、すべてのインスタンスフィールドを一時的に宣言する必要があります(シリアライゼーション攻撃を防ぐために)そしてreadResolve
メソッドを提供する。
シングルトン保証を維持するには、すべてのインスタンスフィールドを一時的に宣言し、
readResolve
メソッドを提供する必要があります(項目77)。それ以外の場合は、シリアル化されたインスタンスが逆シリアル化されるたびに、新しいインスタンスが作成され、この例の場合、エルビスの偽の目撃情報を導きます。これを防ぐには、このreadResolve
メソッドをElvisクラスに追加します。_// readResolve method to preserve singleton property private Object readResolve() { // Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator. return INSTANCE; }
_
これについては、効果的なJava(シリアル化攻撃も示している))で詳しく説明しています。
シングルトンをシリアル化するシナリオ
たとえば、一時的な短期ストレージの場合や、ネットワーク経由でオブジェクトを転送する場合(RMIなど)。
また、オブジェクトをシリアル化できないクラスを設計することは可能です。
他の人が言ったように、Serializable
を実装しないでください。また、オブジェクトまたはそのスーパークラスの1つがSerializable
を実装している場合でも、NotSerializableException
をwriteObject()
からスローすることで、オブジェクトがシリアル化されないようにすることができます。
私はそう言った
デフォルトではありません。 _Java.io.Serializable
_ の実装の次に、readObject()
をオーバーライドする必要があります。 writeObject()
readResolve()
メソッド。静的フィールドをシリアル化できないため。シングルトンはそのインスタンスを静的フィールドに保持します。
しかし、どのシナリオでシングルトンをシリアル化する必要があります。
実際に役立つ現実のシナリオは思い浮かびません。シングルトンは通常、存続期間を通じて状態が変化せず、保存/復元するany状態も含みません。もしそうなら、シングルトンにするのはすでに間違っていた。
Java SE APIのシングルトンパターンの2つの実例は Java.lang.Runtime#getRuntime()
および Java.awt.Desktop#getDesktop()
です。シリアライズ可能を実装するものはありません。また、呼び出しごとに右/望ましい/期待されるインスタンスを正しく返すため、意味を成しません。シリアライズとデシリアライズを行うと、複数のインスタンスが生成される可能性がありますが、その間に環境から切り替えると、インスタンスがまったく機能しない可能性があります。
また、オブジェクトをシリアル化できないクラスを設計することは可能ですか?
はい。クラスに _Java.io.Serializable
_ インターフェースを実装させないでください。
シングルトンのシリアル化の問題は、元のコピーと逆シリアル化されたコピーの2つになってしまうことです。
直列化を防ぐ最も明白な方法は、単純に直列化を実装しないことです。ただし、シリアル化されたオブジェクトで問題なく参照できるように、シングルトンにserializableを実装したい場合があります。
それが問題である場合、いくつかのオプションがあります。可能であれば、シングルトンを単一メンバーの列挙型にするのが最善です。このようにして、基礎となるJava実装がすべての詳細を処理します。
それが不可能な場合は、適切なreadObjectメソッドとwriteObjectメソッドを実装して、シリアル化によって個別のコピーが作成されないようにする必要があります。
シリアル化可能なクラスは、逆シリアル化することでインスタンス化でき、複数のインスタンスを許可して、シングルトンにできません。
Java doc for Java.io.Serializable
クラスの直列化可能性は、Java.io.Serializableインターフェースを実装するクラスによって有効になります。
したがって、シリアライズ可能でないクラスを実装するには、シリアライズ可能を実装しないでください。