在采访中,采访者问我以下问题:是否可以序列化单个对象?我说是的,但在哪种情况下我们应该序列化一个单身?
是否可以设计一个无法序列化对象的类?
答案 0 :(得分:23)
问题应该更好地表达为“是否可以以不破坏单例模式的方式使用单例模式类 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
的多个实例。但是,只引用了一个。其他符合垃圾收集条件,仅由反序列化运行时创建,因此它们不存在用于实际目的。
对于其他两个问题的答案(在哪种情况下我们应该序列化一个单例?是否可以设计一个其对象无法序列化的类?),请参阅@Michael Borgwardt's answer。
答案 1 :(得分:22)
在哪种情况下我们应该序列化 单身人士。
想象一下,你有一个长期运行的应用程序,并希望能够将其关闭,然后在关闭时继续(例如,为了进行硬件维护)。如果应用程序使用有状态的单例,则必须能够保存和恢复sigleton的状态,这最容易通过序列化来完成。
是否可以设计一个无法序列化对象的类。
确实非常简单:只是不要实施Serializable
并制作课程final
答案 2 :(得分:7)
是否可以序列化单例对象?
这取决于单身人士的实施方式。如果您的单例实现为具有一个元素的枚举类型,则默认情况下为:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
如果您的单例不是使用单元素枚举类型实现的,而是使用静态工厂方法(变体是使用公共静态最终字段):
// 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
以使其可序列化是不够的,您必须声明所有实例字段瞬态(以防止序列化攻击)并提供readResolve
方法。
维持单身人士保证, 你必须声明所有实例 瞬态并提供一个
readResolve
方法(第77项)。 否则,每次序列化 实例被反序列化,一个新的 实例将被创建,领导,进入 我们的例子,虚假的情况 猫王目击事件。为防止这种情况,请添加 对猫王的这种readResolve
方法 类:// 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; }
在Effective Java(也显示序列化攻击)中对此进行了大量讨论:
我们应该在哪种情况下序列化单例
例如,用于临时,短期存储或通过网络传输对象(例如,使用RMI)。
是否可以设计一个无法序列化对象的类。
正如其他人所说,不要实施Serializable
。即使某个对象或其中一个超类实现了Serializable
,您仍然可以通过从NotSerializableException
抛出writeObject()
来阻止它被序列化。
答案 3 :(得分:5)
我说是的
默认不是。在实施java.io.Serializable
旁边,您需要覆盖readObject()
和 writeObject()
readResolve()
方法,因为您无法序列化静态字段。单例将其实例保存在静态字段中。
但在哪种情况下我们应该序列化一个单身人士。
实际上并没有想到有用的真实场景。单例通常在其整个生命周期内不会更改状态,也不包含您要保存/恢复的任何状态。如果确实如此,那么已经错误使其成为单身人士。
Java SE API中两个单例模式的真实示例是java.lang.Runtime#getRuntime()
和java.awt.Desktop#getDesktop()
。它们都没有实现可序列化。它也没有任何意义,因为它们在每次调用时都返回正确/期望/预期的实例。如果序列化和反序列化,最终可能会出现多个实例。如果同时从环境切换,实例可能根本不起作用。
是否可以设计一个无法序列化对象的类。
是。只是不要让类实现java.io.Serializable
接口。
答案 4 :(得分:4)
序列化单例的问题在于你最终得到了两个,原始的和反序列化的副本。
防止序列化最明显的方法是不实现序列化。但是,有时您会希望您的单例实现可序列化,以便可以在序列化对象中引用它而不会出现问题。
如果这是一个问题,您有几个选择。如果可能的话,最好是使单例成为单个成员枚举。这样底层的Java实现就可以处理所有细节。
如果无法做到这一点,那么您需要实现相应的readObject和writeObject方法,以确保序列化不会生成单独的副本。
答案 5 :(得分:1)
可序列化的类可以通过反序列化来实例化,允许多个实例,使其不是单例。
至第二个问题,来自java.io.Serializable
的java文档启用了类的可序列化 由实施该课程的班级 java.io.Serializable接口。
因此,要实现一个不可序列化的类,请不要实现Serializable。