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

sub*_*his 26 java singleton serialization

在一次采访中,面试官问我以下问题:是否有可能序列化单个对象?我说是的,但在哪种情况下我们应该序列化一个单身人士?

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

Mic*_*rdt 23

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

想象一下,你有一个长期运行的应用程序,并希望能够关闭它,然后继续关闭它(例如,为了进行硬件维护).如果应用程序使用有状态的单例,则必须能够保存和恢复sigleton的状态,这最容易通过序列化来完成.

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

确实非常简单:只是不要实现Serializable和制作类final

  • @LB,它不仅仅是糟糕的设计,它通常在语义上没有意义.静态字段与实例无关.反序列化涉及填充实例.但是,糟糕的设计IMO完全处于阶级状态:状态应该仅限于实例. (6认同)

Dan*_*ien 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

但要注意,它可能的多个实例AppState使用此代码存在.但是,只引用了一个.其他符合垃圾收集条件,仅由反序列化运行时创建,因此它们不存在用于实际目的.

对于其他两个问题的答案(在哪种场景中我们应该序列化一个单例?是否可以设计一个其对象无法序列化的类?),请参阅@Michael Borgwardt的答案.


Pas*_*ent 7

是否可以序列化单例对象?

这取决于单身人士的实施方式.如果您的单例实现为具有一个元素的枚举类型,则默认情况下:

// Enum singleton - the preferred approach
public enum Elvis {
    INSTANCE;
    public void leaveTheBuilding() { ... }
}
Run Code Online (Sandbox Code Playgroud)

如果您的单例不是使用单元素枚举类型实现的,而是使用静态工厂方法(变体是使用公共静态最终字段):

// 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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

然后添加implements 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;
}
Run Code Online (Sandbox Code Playgroud)

这在Effective Java中有很多讨论(它也显示了序列化攻击):

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

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

例如,用于临时,短期存储或通过网络传输对象(例如,使用RMI).

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

正如其他人所说,不要实施Serializable.即使对象或其超工具之一Serializable,你仍然可以阻止它抛出一个正在连载NotSerializableExceptionwriteObject().


Bal*_*usC 5

我说是

不是默认的.在实现之后,java.io.Serializable您需要覆盖readObject()和方法,因为您无法序列化静态字段.单例将其实例保存在静态字段中.writeObject() readResolve()

但在哪种情况下我们应该序列化一个单身人士.

实际上没有想到有用的现实世界场景.单例通常在其整个生命周期内不会改变状态,也不包含您想要保存/恢复的任何状态.如果确实如此,那么将其作为单身人士已经是错误的.

Java SE API中两个单例模式的真实示例是java.lang.Runtime#getRuntime()java.awt.Desktop#getDesktop().它们都没有实现可序列化.它也没有任何意义,因为它们在每次调用时都返回正确/期望/预期的实例.如果序列化和反序列化,最终可能会出现多个实例.如果同时从环境切换,则实例可能根本不起作用.

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

是.只是不要让类实现java.io.Serializable接口.

  • @BalusC:拥有州是首选单身人士存在的主要原因; 如果它没有状态,你也可以使用静态方法. (4认同)
  • 您只需要对实例的引用,例如由getInstance()方法分发的实例. (2认同)
  • @BalusC:实际上你需要覆盖`readReplace()`以便在多次反序列化时返回单个实例. (2认同)