Подтвердить что ты не робот

Вопрос с интервью: о сериализации Java и синглонах

В интервью интервьюер спросил меня следующий вопрос: возможно ли сериализовать одноэлементный объект? Я сказал "да", но в каком сценарии мы должны сериализовать синглтон?

А можно ли создать класс, объект которого не может быть сериализован?

4b9b3361

Ответ 1

Вопрос, вероятно, должен быть лучше сформулирован как "можно ли использовать сериализацию и десериализацию с помощью класса singleton-pattern 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 ответить.

Ответ 2

в каком сценарии мы должны сериализоваться синглтон.

Представьте, что у вас долгое приложение и вы хотите его закрыть, а затем продолжить в том месте, где оно было отключено (например, для обслуживания оборудования). Если приложение использует одноэлементный режим, который имеет статус "состояние", вам нужно будет сохранить и восстановить состояние сиглетона, что наиболее легко сделать путем его сериализации.

И возможно ли создать класс, объект которого не может быть сериализован.

Очень просто: просто не реализуйте Serializable и создайте класс final

Ответ 3

Можно ли сериализовать одноэлементный объект?

Это зависит от того, как реализован синглтон. Если ваш singleton реализован как тип перечисления с одним элементом, то он по умолчанию:

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

Если ваш singleton не реализован с использованием одноэлементного типа перечисления, но, скажем, с помощью статического метода factory (вариант заключается в использовании открытого статического конечного поля):

// 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: Принудительное свойство singleton с частным конструктором или типом перечисления
  • Пункт 77: например, элемент управления, предпочитайте типы перечисления для readResolve

в каком сценарии мы должны сериализовать singleton

Например, для временного, краткосрочного хранения или для транспортировки объектов по сети (например, с RMI).

И возможно ли создать класс, объект которого не может быть сериализован.

Как говорили другие, не реализуйте Serializable. И даже если объект или один из его суперклассов реализует Serializable, вы все равно можете помешать его сериализации, выбросив NotSerializableException из writeObject().

Ответ 4

я сказал да

Не по умолчанию. Рядом с реализацией java.io.Serializable вам необходимо переопределить методы readObject() и writeObject() readResolve(), потому что вы не можете сериализовать статические поля. Синтаксис содержит свой экземпляр в статическом поле.

но в этом сценарии мы должны сериализовать синглтон.

Никакой полезный сценарий реального мира не приходит в голову. Синглтон обычно не изменяет состояние на протяжении всей своей жизни и не содержит состояния, которое вы хотите сохранить/восстановить. Если бы это было так, то было бы уже неправильно сделать его одиночным.

Два реальных примера одноэлементного шаблона в Java SE API: java.lang.Runtime#getRuntime() и java.awt.Desktop#getDesktop(). Ни один из них не реализует сериализуемый. Это также не имеет никакого смысла, поскольку они правильно возвращают правильный/желаемый/ожидаемый экземпляр при каждом вызове. Если вы выполните сериализацию и десериализацию, вы можете получить несколько экземпляров. Если вы переключитесь с среды в то же время, экземпляр может вообще не работать.

И возможно ли создать класс, объект которого не может быть сериализован.

Да. Просто не позволяйте классу реализовывать интерфейс java.io.Serializable.

Ответ 5

Проблема с сериализации синглтона заключается в том, что вы получаете две, оригинальную и десериализованную копию.

Самый очевидный способ предотвратить сериализацию - просто не реализовать сериализуемое. Однако иногда вам нужно, чтобы ваш singleton реализовал сериализуемый интерфейс, чтобы на него можно было ссылаться в сериализованном объекте без проблем.

Если это проблема, у вас есть несколько вариантов. Лучше всего сделать одноэлементным единственным перечислением участника, если это возможно. таким образом, основная реализация Java выполняет все детали.

Если это невозможно, вам необходимо реализовать соответствующие методы readObject и writeObject, чтобы гарантировать, что сериализация не делает отдельную копию.

Ответ 6

Класс, который является сериализуемым, может быть создан путем десериализации его, что позволяет использовать несколько экземпляров, что делает его не одиночным.

к вашему второму вопросу, из java doc для java.io.Serializable

Сериализуемость класса включена классом, реализующим java.io.Serializable интерфейс.

Итак, чтобы реализовать класс, который не является сериализуемым, не реализуйте Serializable.