面试题:关于Java序列化和单例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2958863/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:56:11  来源:igfitidea点击:

Interview question: about Java serialization and singletons

javaserializationsingleton

提问by subhashis

In an interview the interviewer asked me the following question: is it possible to serialize a singleton object? I said yes, but in which scenario should we serialize a singleton?

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

And is it possible to design a class whose object can not be serialized?

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

采纳答案by Daniel Trebbien

The question should probably be better phrased as "is it possible to use serialization and deserialization with a singleton-pattern class Cin a way that does not break the singleton pattern?"

这个问题可能应该更好地表述为“是否有可能以不破坏单例模式的方式对单例模式类C使用序列化和反序列化?”

The answer is basically yes:

答案基本上是肯定的:

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());
    }
}

but note that it ispossible for multiple instances of AppStateto exist using this code. However, only one is referenced. The others are eligible for garbage collection, created only by the deserialization runtime, so they don't exist for practical purposes.

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

For answers to your other two questions (In which scenario should we serialize a singleton? Is it possible to design a class whose object can not be serialized?), see @Michael Borgwardt's answer.

有关其他两个问题的答案(在哪种情况下我们应该序列化单例?是否可以设计一个对象无法序列化的类?),请参阅@Michael Borgwardt 的回答

回答by BalusC

i said yes

我说是

Not by default. Next to implementing java.io.Serializableyou need to override readObject()and writeObject()readResolve()methods, because you cannot serialize static fields. A singleton holds its instance in a static field.

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

but in which scenario we should serialize a singleton.

但是在哪种情况下我们应该序列化一个单例。

No useful real world scenario comes to mind actually. A singleton usually doesn't change of state throughout its lifetime nor contains anystate which you'd like to save/restore. If it did, then it was alreadywrong to make it a singleton.

实际上没有想到有用的现实世界场景。单例通常不会在其整个生命周期内更改状态,也不包含您想要保存/恢复的任何状态。如果是这样,那么让它成为单例就已经是错误的了。

Two real world examples of singleton pattern in Java SE API are java.lang.Runtime#getRuntime()and java.awt.Desktop#getDesktop(). None of them implements serializable. It also doesn't make any sensebecause they right returns the right/desired/expected instance on every call. If you serialize and deserialize, you may end up with multiple instances. If you switch from environment in meanwhile, the instance may not work at all.

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

And is it possible to design a class whose object can not be serialized.

以及是否可以设计一个对象不能序列化的类。

Yes. Just don't let the class implement java.io.Serializableinterface.

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

回答by Michael Borgwardt

in which scenario we should serialize a singleton.

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

Imagine you have a long-running app and want to be able to shut it down and later continue at the point where it was shut down (e.g. in order to do hardware maintenance). If the app uses a singleton that is stateful, you'd have to be able to save and restore the sigleton's state, which is most easily done by serializing it.

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

And is it possible to design a class whose object can not be serialized.

以及是否可以设计一个对象不能序列化的类。

Very simple indeed: just don't implement Serializableand make the class final

确实很简单:只是不要实现Serializable和创建类final

回答by Angelo Genovese

A class that is serializable can be instantiated by deserializing it, allowing for multiple instances, making it not a singleton.

可序列化的类可以通过反序列化来实例化,允许多个实例,使其不是单例。

to your second question, from the java doc for java.io.Serializable

到你的第二个问题,来自java.io.Serializable的 java doc

Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

类的可序列化由实现 java.io.Serializable 接口的类启用。

So to implement a class which is not serializable, don't implement Serializable.

所以要实现一个不可序列化的类,不要实现 Serializable。

回答by Yishai

The problem with serializing a singleton is that you end up with two, the original and the deserialized copy.

序列化单例的问题在于您最终会得到两个,原始副本和反序列化副本。

The most obvious way to prevent serialization is to simply not implement serializable. However, sometimes you will want your singleton to implement serializable so that it can be referenced in a serialized object without issues.

防止序列化的最明显方法是简单地不实现可序列化。但是,有时您会希望单例实现可序列化,以便可以在序列化对象中引用它而不会出现问题。

If that is a problem, you have a few options. The best is to make the singleton a single member enum, if possible. that way the underlying Java implementation takes care of all the details.

如果这是一个问题,您有几个选择。如果可能的话,最好的办法是使单例成为单个成员枚举。这样底层的 Java 实现就会处理所有的细节。

If that is not possible, then you need to implement the appropriate readObject and writeObject methods to ensure that serialization does not make a separate copy.

如果这是不可能的,那么您需要实现适当的 readObject 和 writeObject 方法以确保序列化不会制作单独的副本。

回答by Pascal Thivent

is it possible to serialize a singleton object?

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

It depends on how the singleton is implemented. If your singleton is implemented as an enum type with one element, then it is by default:

这取决于单例的实现方式。如果你的单例被实现为一个包含一个元素的枚举类型,那么它默认是:

// Enum singleton - the preferred approach
public enum Elvis {
    INSTANCE;
    public void leaveTheBuilding() { ... }
}

If your singleton is not implemented using a single-element enum type but, say using a static factory method (the variant is to use a public static final field):

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

// 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() { ... }
}

Then it is not sufficient to add implements Serializableto make it serializable, you must declare all instance fields transient(to prevent a serialization attack) and provide a readResolvemethod.

然后添加implements Serializable使其可序列化是不够的,您必须声明所有实例字段都是瞬态的(以防止序列化攻击)并提供readResolve方法

To maintain the singleton guarantee, you have to declare all instance fields transient and provide a readResolvemethod (Item 77). Otherwise, each time a serialized instance is deserialized, a new instance will be created, leading, in the case of our example, to spurious Elvis sightings. To prevent this, add this readResolvemethod to the Elvis class:

// 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;
}

为了维护单例保证,您必须声明所有实例字段都是瞬态的并提供一个 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;
}

This is heavily discussed in Effective Java (which also shows the serialization attack):

这在 Effective Java 中有大量讨论(也展示了序列化攻击):

  • Item 3: Enforce the singleton property with a private constructor or an enum type
  • Item 77: For instance control, prefer enum types to readResolve
  • 第 3 条:使用私有构造函数或枚举类型强制实现单例属性
  • 第 77 条:例如控制,更喜欢 enum 类型而不是 readResolve

in which scenario should we serialize a singleton

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

For example for temporary, short-term storage or for transporting objects over a network (with RMI, for example).

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

And is it possible to design a class whose object can not be serialized.

以及是否可以设计一个对象不能序列化的类。

As others said, don't implement Serializable. And even if an object or one of its superclasses implements Serializable, you can still prevent it from being serialized by throwing a NotSerializableExceptionfrom writeObject().

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