采访问题:关于Java序列化和单例

时间:2010-06-02 14:54:13

标签: java serialization singleton

在采访中,采访者问我以下问题:是否可以序列化单个对象?我说是的,但在哪种情况下我们应该序列化一个单身?

是否可以设计一个无法序列化对象的类?

6 个答案:

答案 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(也显示序列化攻击)中对此进行了大量讨论:

  • 第3项:使用私有构造函数或枚举类型
  • 强制执行单例属性
  • 第77项:对于实例控制,首选枚举类型为readResolve
  

我们应该在哪种情况下序列化单例

例如,用于临时,短期存储或通过网络传输对象(例如,使用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。