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

1
Q

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

A

Сериализация — это процесс сохранения состояния объекта в последовательность байт.
Десериализация — это процесс восстановления объекта из этих байт.

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

Сериализация позволяет в определенных пределах изменять класс. Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически:
● добавление в класс новых полей;
● изменение полей из статических в нестатические;
● изменение полей из транзитных в нетранзитные.
Обратные изменения (из нестатических полей в статические и из нетранзитных в
транзитные) или удаление полей требуют определенной дополнительной обработки в зависимости от того, какая степень обратной совместимости необходима.

https: //javarush.ru/groups/posts/2022-serializacija-i-deserializacija-v-java
https: //www.youtube.com/watch?v=PcwXTCWRGvY

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

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

A
  • чтобы сохранить и затем восстановить состояние программы;
  • чтобы передавать объекты по сети использую протоколы передачи данных

Бинарный формат - это проще всего и самый быстрый формат для обмена

Сериализация позволяет в определенных пределах изменять класс. Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически:
- добавление в класс новых полей;
- изменение полей из статических в нестатические;
- изменение полей из транзитных в нетранзитные.
Обратные изменения (из нестатических полей в статические и из нетранзитных в транзитные) или удаление полей требуют определенной дополнительной обработки в зависимости от того, какая степень обратной совместимости необходима.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Опишите процесс сериализации/десериализации с использованием Serializable.

A

Интерфейс-маркер(вместо аннотаций)
Сохранение объекта в последовательность байт
Запись/чтение через ObjectOutputStream/ObjectInputStream
Записываются:

При использовании Serializable применяется алгоритм сериализации, который с помощью рефлексии (Reflection API) выполняет:
- запись в поток метаданных о классе, ассоциированном с объектом (имя класса, идентификатор SerialVersionUID, идентификаторы полей класса);
- рекурсивную запись в поток описания суперклассов до класса java.lang.Object (не включительно);
- запись примитивных значений полей сериализуемого экземпляра, начиная с полей самого верхнего суперкласса;
- рекурсивную запись объектов, которые являются полями сериализуемого объекта.
При этом ранее сериализованные объекты повторно не сериализуются, что позволяет алгоритму корректно работать с циклическими ссылками.

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

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

A

Реализовать интерфейс java.io.Externalizable, который позволяет применение пользовательской логики сериализации. Способ сериализации и десериализации описывается в методах writeExternal() и readExternal(). Во время десериализации вызывается конструктор без параметров, а потом уже на созданном объекте вызывается метод readExternal.
Если у сериализуемого объекта реализован один из следующих методов, то механизм сериализации будет использовать его, а не метод по умолчанию :
writeObject() - запись объекта в поток;
readObject() - чтение объекта из потока;
writeReplace() - позволяет заменить себя экземпляром другого класса перед
записью;
readResolve() - позволяет заменить на себя другой объект после чтения.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

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

сериализовано final поле?

A

Использование при описании поля класса модификатора transient позволяет исключить указанное поле из сериализации. Это бывает полезно для секретных (пароль) или не особо важных данных. Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить. В результате сериализации и десериализации адрес объекта принимает значение по умолчанию или будет null.

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

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Переопределение сериализации в интерфейсе Serializable

A

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

writeObject - запись объекта в поток;
readObject - чтение объекта из потока;
writeReplace - позволяет заменить себя экземпляром другого класса перед записью;
readResolve - позволяет заменить на себя другой объект после чтения.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Какое влияние оказывают на сериализуемость модификаторы полей static и final?

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

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

A

Чтобы не допустить автоматическую сериализацию можно переопределить private методы для создания исключительной ситуации NotSerializableException.
private void writeObject(ObjectOutputStream out) throws IOException { throw new NotSerializableException();
}
private void readObject(ObjectInputStream in) throws IOException { throw new NotSerializableException();
}
Любая попытка записать или прочитать этот объект теперь приведет к возникновению исключительной ситуации.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

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

A

Для создания собственного протокола сериализации достаточно реализовать интерфейс Externalizable, который содержит два метода:
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

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

A

serialVersionUID используется для указании версии сериализованных данных.
Когда мы не объявляем serialVersionUID в нашем классе явно, среда выполнения Java делает это за нас, но этот процесс чувствителен ко многим метаданным класса включая количество полей, тип полей, модификаторы доступа полей, интерфейсов, которые реализованы в классе и пр.
Рекомендуется явно объявлять serialVersionUID т.к. при добавлении, удалении атрибутов класса динамически сгенерированное значение может измениться и в момент выполнения будет выброшено исключение InvalidClassException.
private static final long serialVersionUID = 20161013L;

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

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

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

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

A

Проблема в том что после десериализации мы получим другой объект. Таким образом, сериализация дает возможность создать Singleton еще раз, что недопустимо. Существует два способа избежать этого:
явный запрет сериализации.
определение метода с сигнатурой (default/public/private/protected/) Object readResolve() throws ObjectStreamException, назначением которого станет возврат замещающего объекта вместо объекта, на котором он вызван.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Какие существуют способы контроля за значениями десериализованного объекта?

A

Если есть необходимость выполнения контроля за значениями десериализованного объекта, то можно использовать интерфейс ObjectInputValidation с переопределением метода validateObject().
Метод validateObject будет автоматически вызван, когда мы зарегистрируем эту проверку, вызвав ObjectInputStream.registerValidation(this, 0) из метода readObject .
Очень полезно проверить, что поток не был подделан, или что данные имеют смысл, прежде чем передать их обратно в ваше приложение.
// Если вызвать метод validateObject() после десериализации объекта, то будет вызвано исключение InvalidObjectException при значении возраста за пределами 39…60.
public class Person implements java.io.Serializable, java.io.ObjectInputValidation {

@Override
public void validateObject() throws InvalidObjectException {
if ((age < 39) || (age > 60))
throw new InvalidObjectException(“Invalid age”);
} }
Подписывание сериализованных данных
Чтобы убедиться, что данные не были изменены в файле или при пересылке по сети их можно «подписать». Несмотря на то, что управление подписями реализовать можно и с помощью методов writeObject и readObject, для этого есть более подходящий способ.

Если требуется зашифровать и подписать объект, то проще всего поместить его в оберточный класс javax.crypto.SealedObject и/или java.security.SignedObject. Данные классы являются сериализуемыми, поэтому при оборачивании объекта в SealedObject создается подобие “подарочной упаковки” вокруг исходного объекта. Для шифрования необходимо создать симметричный ключ, управление которым должно осуществляться отдельно. Аналогично, для проверки данных можно использовать класс SignedObject, для работы с которым также нужен симметричный ключ, управляемый отдельно. Эти два объекта позволяют упаковывать и подписывать сериализованные данные, не отвлекаясь на детали проверки и шифрования цифровых подписей.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Что такое Reflection?

A

Рефлексия (Reflection) - это механизм получения данных о программе во время ее выполнения (runtime). В Java Reflection осуществляется с помощью Java Reflection API, состоящего из классов пакетов java.lang и java.lang.reflect.
Возможности Java Reflection API:
● Определение класса объекта;
● Получение информации о модификаторах класса, полях, методах,
конструкторах и суперклассах;
● Определение интерфейсов, реализуемых классом;
● Создание экземпляра класса;
● Получение и установка значений полей объекта;
● Вызов методов объекта;
● Создание нового массива.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Расскажите про клонирование объектов.

A

Для клонирования объекта в Java можно пользоваться тремя способами:
Переопределение метода clone() и реализация интерфейса Cloneable();
Использование конструктора копирования;
Использовать для клонирования механизм сериализации

Интерфейс Cloneable – это так называемый интерфейс-маркер, который не содержит никаких методов. Он используется, чтобы маркировать (помечать) некоторые классы.
Если разработчика не устраивает стандартная реализация метода clone, он должен написать свою, которая будет создавать дубликат объекта правильным образом.
При вызове метода clone(), Java проверяет, был ли у объекта интерфейс Cloneable. Если да — клонирует объект методом clone(), если нет — выкидывает исключение CloneNotSupportedException.

«клонированием по умолчанию»
а) Добавить интерфейс Cloneable своему классу
б) Переопределить метод clone и вызвать в нем базовую реализацию:

Или ты можешь написать реализацию метода clone полностью сам
Если разработчик класса считает, что объекты класса можно клонировать, он помечает класс этим интерфейсом (наследует класс от Cloneable).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Глубокое копирование и поверхностное копирование

A

Точной копией оригинала является его клон. В Java это означает возможность создавать объект с аналогичной структурой, как и у исходного объекта. Метод clone() обеспечивает эту функциональность.

Поверхностное копирование копирует настолько малую часть информации, насколько это возможно. По умолчанию, клонирование в Java является поверхностным, т.е. Object class не знает о структуре класса, которого он копирует. При клонировании, JVM делает такие вещи:
Если класс имеет только члены примитивных типов, то будет создана совершенно новая копия объекта и возвращена ссылка на этот объект.
Если класс содержит не только члены примитивных типов, а и любого другого типа класса, тогда копируются ссылки на объекты этих классов. Следовательно, оба объекта будут иметь одинаковые ссылки.
Глубокое копирование дублирует все. Глубокое копирование — это две коллекции, в одну из которых дублируются все элементы оригинальной коллекции. Мы хотим сделать копию, при которой внесение изменений в любой элемент копии не затронет оригинальную коллекцию.

Глубокое клонирование требует выполнения следующих правил:
Нет необходимости копировать отдельно примитивные данные;
Все классы-члены в оригинальном классе должны поддерживать клонирование. Для каждого члена класса должен вызываться super.clone() при переопределении метода clone();
Если какой-либо член класса не поддерживает клонирование, то в методе клонирования необходимо создать новый экземпляр этого класса и скопировать каждый его член со всеми атрибутами в новый объект класса, по одному.

17
Q

В чем отличие между поверхностным и глубоким клонированием?

A

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

Глубокое копирование дублирует абсолютно всю информацию объекта: Нет необходимости копировать отдельно примитивные данные;
Все члены ссылочного типа в оригинальном классе должны поддерживать
клонирование. Для каждого такого члена при переопределении метода clone() должен вызываться super.clone();
Если какой-либо член класса не поддерживает клонирование, то в методе клонирования необходимо создать новый экземпляр этого класса и скопировать каждый его член со всеми атрибутами в новый объект класса, по одному.

18
Q

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

A

Наиболее безопасным и следовательно предпочтительным способом клонирования является использование специализированного конструктора копирования:
Отсутствие ошибок наследования (не нужно беспокоиться, что у наследников появятся новые поля, которые не будут склонированы через метод clone());
Поля для клонирования указываются явно; Возможность клонировать даже final поля.

19
Q

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

A

Метод clone() объявлен в классе Object с указанием модификатора native, чтобы обеспечить доступ к стандартному механизму поверхностного копирования объектов. Одновременно он объявлен и как protected, чтобы нельзя было вызвать этот метод у не переопределивших его объектов. Непосредственно интерфейс Cloneable является маркерным (не содержит объявлений методов) и нужен только для обозначения самого факта, что данный объект готов к тому, чтобы быть клонированным. Вызов переопределённого метода clone() у не Cloneable объекта вызовет выбрасывание CloneNotSupportedException.

20
Q

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

A

Конструктор копирования
Переопределение clone()
Сериализовать десериализовать
То же через Gson

21
Q

Дайте определение понятию «конструктор».

A

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