Сериализация и копирование

Вопросы к собеседованию

Вопросы:

Сериализация

Что такое сериализация и как она реализована в Java?

Сериализация (Serialization) - процесс преобразования структуры данных в линейную последовательность байтов для дальнейшей передачи или сохранения.

Сериализованные объекты можно затем восстановить (десериализовать).

В Java, согласно спецификации Java Object Serialization существует два стандартных способа сериализации: стандартная сериализация, через использование интерфейса java.io.Serializable и «расширенная» сериализация - java.io.Externalizable.

Сериализация позволяет в определенных пределах изменять класс.

Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически:

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

Сериализация работает быстрее, чем JSON и XML, за счет того что это бинарные данные (байты).

Для чего нужна сериализация?

для передачи или сохранения объектов (данных).

Процесс сериализации/десериализации с использованием Serializable

При использовании Serializable применяется алгоритм сериализации, который с помощью рефлексии (Reflection API) выполняет:

Рефлексия (от позднелат. reflexio — обращение назад) — это механизм исследования данных о программе во время её выполнения.

Рефлексия позволяет исследовать информацию о полях, методах и конструкторах классов.

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

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

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

Для сериализации объектов в поток используется класс ObjectOutputStream.

void writeObject(Object obj) - записывает в поток отдельный объект

void close() - закрывает поток

void flush() - очищает буфер и сбрасывает его содержимое в выходной поток

Для десериализации – ObjectInputStream.

Object readObject()считывает из потока объект

void close() - закрывает поток

Изменение и настройка сериализации


Как изменить стандартное поведение сериализации/десериализации?

Реализовать интерфейс java.io.Externalizable, который позволяет применение пользовательской логики сериализации.

Способ сериализации и десериализации описывается в методах writeExternal() и readExternal().

Во время десериализации вызывается конструктор без параметров, а потом уже на созданном объекте вызывается метод readExternal.

Для «мегакастомной» сериализации. Не пишется мета-информация и инфа о суперклассах.

Если у сериализуемого объекта реализован один из следующих методов, то механизм сериализации будет использовать его, а не метод по умолчанию :

writeObject() - запись объекта в поток

readObject() - чтение объекта из потока

writeReplace() - позволяет заменить себя экземпляром другого класса перед записью

readResolve() - озволяет заменить на себя другой объект после чтения


Будет ли сериализовано final поле?

Да


Какие поля не будут сериализованы при сериализации?

Поля класса, помеченные модификатором transient, не сериализуются.

Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить. Другой пример такого поля - ссылка на экземпляр объекта, который не требует сериализации или не может быть сериализован.

При стандартной сериализации поля, имеющие модификатор static, не сериализуются.

Соответственно, после десериализации это поле значения не меняет.

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

Поля с модификатором final сериализуются как и обычные.

За одним исключением – их невозможно десериализовать при использовании Externalizable, поскольку final поля должны быть инициализированы в конструкторе, а после этого в readExternal() изменить значение этого поля будет невозможно. Соответственно, если необходимо сериализовать объект с final полем необходимо использовать только стандартную сериализацию.


Как создать собственный протокол сериализации?

Для создания собственного протокола сериализации достаточно реализовать интерфейс Externalizable, который содержит два метода:

public void writeExternal(ObjectOutput out) throws IOException;

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException


Какая роль поля serialVersionUID в сериализации?

serialVersionUID используется для указании версии сериализованных данных.

Когда мы не объявляем serialVersionUID в нашем классе явно, среда выполнения Java делает это за нас, но этот процесс чувствителен ко многим метаданным класса включая количество полей, тип полей, модификаторы доступа полей, интерфейсов, которые реализованы в классе и пр.

Рекомендуется явно объявлять serialVersionUID т.к. при добавлении, удалении атрибутов класса динамически сгенерированное значение может измениться и в момент выполнения будет выброшено исключение InvalidClassException.

private static final long serialVersionUID = 20161013L;

Когда стоит изменять значение поля serialVersionUID?

serialVersionUID нужно изменять при внесении в класс несовместимых изменений, например при удалении какого-либо его атрибута.


Как не допустить сериализацию

Чтобы не допустить автоматическую сериализацию можно переопределить private методы для создания исключительной ситуации NotSerializableException.

private void writeObject(ObjectOutputStream out) throws IOException {

throw new NotSerializableException();

}

private void readObject(ObjectInputStream in) throws IOException {

throw new NotSerializableException();

}

Любая попытка записать или прочитать этот объект теперь приведет к возникновению исключительной ситуации.


В чем проблема сериализации Singleton?

Проблема в том, что после десериализации мы получим другой объект. Таким образом, сериализация дает возможность создать Singleton еще раз, что недопустимо.

Существует два способа избежать этого:

Клонирование объектов

3 СПОСОБА

...
...
...

Какой способ клонирования предпочтительней?

Наиболее безопасным и следовательно предпочтительным способом клонирования является использование специализированного конструктора копирования:

.


Почему метод clone() объявлен в классе Object, а не в интерфейсе Cloneable?

Метод clone() объявлен в классе Object с указанием модификатора native, чтобы обеспечить доступ к стандартному механизму поверхностного копирования объектов.

Одновременно он объявлен и как protected, чтобы нельзя было вызвать этот метод у не переопределивших его объектов.

Непосредственно интерфейс Cloneable является маркерным (не содержит объявлений методов) и нужен только для обозначения самого факта, что данный объект готов к тому, чтобы быть клонированным.

Вызов переопределённого метода clone() у не Cloneable объекта вызовет выбрасывание CloneNotSupportedException.


Как создать глубокую копию объекта?

Два способа:

  1. через конструктор копирования
  2. через перепределение clone()