Зачем используется Serial Version UID? Что если не определить его?
Сериализуемый класс явно или неявно, но всегда имеет serialVersionUID . Это число типа long , которое представляет собой «версию» сериализационной формы класса. Если при сериализации/десериализации значения serialVersionUID не совпадают – будет выброшено InvalidClassException .
Для совпадающих версий работает мощная поддержка эволюции класса – совместимые изменения, такие как добавление или удаление полей, не приводят к InvalidClassException .
Неявное значение вычисляется автоматически в рантайме, и включает в себя информацию о имени типа, списке родителей и полей (с точностью до коллизии). По смыслу это похоже на хэш-сумму класса. Соответственно, при любом изменении класса значение изменится, и поддержка эволюции окажется бесполезной.
Всегда лучше явно указывать любое значение serialVersionUID , и изменять только в тех редких случаях, когда требуется сломать совместимость с предыдущими версиями. Стандартная утилита JDK serialver умеет «угадывать» авто-генерированное значение. Она используется чтобы зафиксировать значение для включения поддержки эволюции созданного ранее класса.
Явное значение устанавливается в переменную static final long serialVersionUID .
Сериализация в Java
Сериализация (Serialization) — процесс преобразования структуры данных в линейную последовательность байтов для дальнейшей передачи или сохранения. Сериализованные объекты можно затем восстановить (десериализовать).
В Java, согласно спецификации Java Object Serialization существует два стандартных способа сериализации: стандартная сериализация, через использование интерфейса java.io.Serializable и «расширенная» сериализация — java.io.Externalizable .
Сериализация позволяет в определенных пределах изменять класс. Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически:
- добавление в класс новых полей;
- изменение полей из статических в нестатические;
- изменение полей из транзитных в нетранзитные.
Обратные изменения (из нестатических полей в статические и из нетранзитных в транзитные) или удаление полей требуют определенной дополнительной обработки в зависимости от того, какая степень обратной совместимости необходима.
Опишите процесс сериализации/десериализации с использованием Serializable
При использовании Serializable применяется алгоритм сериализации, который с помощью рефлексии (Reflection API) выполняет:
- запись в поток метаданных о классе, ассоциированном с объектом (имя класса, идентификатор SerialVersionUID , идентификаторы полей класса);
- рекурсивную запись в поток описания суперклассов до класса java.lang.Object (не включительно);
- запись примитивных значений полей сериализуемого экземпляра, начиная с полей самого верхнего суперкласса;
- рекурсивную запись объектов, которые являются полями сериализуемого объекта.
При этом ранее сериализованные объекты повторно не сериализуются, что позволяет алгоритму корректно работать с циклическими ссылками.
Для выполнения десериализации под объект выделяется память, после чего его поля заполняются значениями из потока. Конструктор объекта при этом не вызывается. Однако при десериализации будет вызван конструктор без параметров родительского несериализуемого класса, а его отсутствие повлечёт ошибку десериализации.
Как изменить стандартное поведение сериализации/десериализации?
- Реализовать интерфейс java.io.Externalizable , который позволяет применение пользовательской логики сериализации. Способ сериализации и десериализации описывается в методах writeExternal() и readExternal() . Во время десериализации вызывается конструктор без параметров, а потом уже на созданном объекте вызывается метод readExternal .
- Если у сериализуемого объекта реализован один из следующих методов, то механизм сериализации будет использовать его, а не метод по умолчанию :
- writeObject() — запись объекта в поток;
- readObject() — чтение объекта из потока;
- writeReplace() — позволяет заменить себя экземпляром другого класса перед записью;
- readResolve() — позволяет заменить на себя другой объект после чтения.
Как исключить поля из сериализации?
Для управления сериализацией при определении полей можно использовать ключевое слово transient , таким образом исключив поля из общего процесса сериализации.
Что обозначает ключевое слово transient ?
Поля класса, помеченные модификатором transient , не сериализуются.
Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить. Другой пример такого поля — ссылка на экземпляр объекта, который не требует сериализации или не может быть сериализован.
Какое влияние оказывают на сериализуемость модификаторы полей static и final
При стандартной сериализации поля, имеющие модификатор static, не сериализуются. Соответственно, после десериализации это поле значения не меняет. При использовании реализации Externalizable сериализовать и десериализовать статическое поле можно, но не рекомендуется этого делать, т.к. это может сопровождаться трудноуловимыми ошибками.
Поля с модификатором final сериализуются как и обычные. За одним исключением – их невозможно десериализовать при использовании Externalizable , поскольку final поля должны быть инициализированы в конструкторе, а после этого в readExternal() изменить значение этого поля будет невозможно. Соответственно, если необходимо сериализовать объект с final полем необходимо использовать только стандартную сериализацию.
Как не допустить сериализацию?
Чтобы не допустить автоматическую сериализацию можно переопределить private методы для создания исключительной ситуации NotSerializableException .
private void writeObject(ObjectOutputStream out) throws IOException throw new NotSerializableException(); > private void readObject(ObjectInputStream in) throws IOException throw new NotSerializableException(); >Любая попытка записать или прочитать этот объект теперь приведет к возникновению исключительной ситуации.
Как создать собственный протокол сериализации?
Для создания собственного протокола сериализации достаточно реализовать интерфейс 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 нужно изменять при внесении в класс несовместимых изменений, например при удалении какого-либо его атрибута.
В чем проблема сериализации Singleton?
Проблема в том что после десериализации мы получим другой объект. Таким образом, сериализация дает возможность создать Singleton еще раз, что недопустимо. Существует два способа избежать этого:
- явный запрет сериализации.
- определение метода с сигнатурой (default/public/private/protected/) Object readResolve() throws ObjectStreamException , назначением которого станет возврат замещающего объекта вместо объекта, на котором он вызван.
Какие существуют способы контроля за значениями десериализованного объекта
Если есть необходимость выполнения контроля за значениями десериализованного объекта, то можно использовать интерфейс ObjectInputValidation с переопределением метода validateObject() .
// Если вызвать метод 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 , для работы с которым также нужен симметричный ключ, управляемый отдельно.
Источники
- IBM developerWorks
- Java-online.ru
- Изучите секреты Java Serialization API
- JavaRush
- Записки трезвого практика
java-interview maintained by timmson
Зачем использовать SerialVersionUID внутри класса Serializable в Java
Serialization и SerialVersionUID всегда остаются загадкой для многих разработчиков Java. Я часто вижу такие вопросы, как этот SerialVersionUID или что произойдет, если я не объявлю SerialVersionUID в своем классе Serializable? Помимо сложности и редкого использования, еще одной причиной этих вопросов является предупреждение Eclipse IDE об отсутствии SerialVersionUID, например «Класс Serializable Customer не объявляет статическое конечное поле SerialVersionUID типа long» . В этой статье вы познакомитесь не только с основами Java SerialVersionUID, но и с его эффектами в процессе сериализации и десериализации. Когда вы объявляете класс как Serializable, реализуя интерфейс маркера java.io.Serializable, среда выполнения Java сохраняет экземпляр этого класса на диск с использованием механизма сериализации по умолчанию, при условии, что вы не настроили процесс с использованием интерфейса Externalizable . Во время сериализации среда выполнения Java создает номер версии для класса, чтобы он мог десериализовать его позже. Этот номер версии известен как SerialVersionUID в Java. Если во время десериализации SerialVersionUID не совпадает, процесс завершится с ошибкой InvalidClassException в качестве исключения в потоке «main» java.io.InvalidClassException , а также напечатает имя класса и соответствующий SerialVersionUID. Быстрое решение этой проблемы – копирование SerialVersionUID и объявление их как частной статической конечной длинной константы в вашем классе. В этой статье мы узнаем о том, почему мы должны использовать SerialVersionUID в Java, и как использовать инструмент JDK serialver для генерации этого идентификатора. Если вы новичок в сериализации, вы также можете увидеть Топ 10 интервью по сериализации Java, чтобы оценить свои знания и найти пробел в своем понимании для дальнейшего чтения. Как и в случае параллелизма и многопоточности, сериализация – это еще одна тема, заслуживающая пару прочтений.
Зачем использовать SerialVersionUID в Java
Как я уже сказал, когда мы не объявляем SerialVersionUID как статическое, конечное и длинное значение в нашем классе, механизм Serialization создает его для нас. Этот механизм чувствителен ко многим деталям, включая поля в вашем классе, их модификатор доступа, интерфейс, который они реализуют, и даже различные реализации компилятора, любые изменения в классе или использование другого компилятора могут привести к различному SerialVersionUID , который многие в конечном итоге прекратят перезагружать сериализованные данные. Слишком рискованно полагаться на механизм сериализации Java для генерации этого идентификатора, и поэтому рекомендуется объявить явный SerialVersionUID в вашем классе Serializable . Я настоятельно рекомендую прочитать классический заголовок Java Джошуа Блоха «Эффективная Java», чтобы понять сериализацию Java и проблемы неправильной обработки. Кстати, JDK также предоставляет инструмент под названием serialver, расположенный в каталоге bin папки JAVA_HOME , на моей машине C: \ Program Files \ Java \ jdk1.6.0_26 \ bin \ serialver.exe, который можно использовать для генерации SerialVersionUID для старых классы. Это очень полезно, если вы внесли изменения в ваш класс, что нарушает сериализацию и ваше приложение не может перезагрузить сериализованные экземпляры. Вы можете просто использовать этот инструмент для создания SerialVersionUID для старых экземпляров, а затем явно использовать его в своем классе, объявив личное , статическое , окончательное и длинное поле SerialVersionUID. Между прочим, настоятельно рекомендуется, как из-за производительности, так и по соображениям безопасности, использовать настраиваемый двоичный формат для сериализации, еще раз в Effective Java есть пара элементов, которые подробно объясняют преимущества пользовательского формата.
Как использовать инструмент serialver JDK для генерации SerialVersionUID
Вы можете использовать инструмент serialver JDK для генерации SerialVersionUID для классов. Это особенно полезно для развивающихся классов, оно возвращает SerialVersionUID в формате, который легко скопировать. Вы можете использовать инструмент serialver JDK, как показано в примере ниже:
Serialversionuid java что это
Сериализация Java
Сериализация Java связана еще с одной важной библиотекой платформы Java. Сериализация в основном используется для персистенции объектов и взаимодействия с удаленными объектами — это два случая, когда нужно иметь возможность создать копию состояния объекта, чтобы позднее восстановить его. В этом разделе содержится краткая информация об API Java Serialization и показано, как использовать его в своих программах.
Что такое сериализация объектов?
Сериализация — это процесс, когда состояние объекта и его метаданные (например, имя класса объекта и имена его атрибутов) сохраняются в особом двоичном формате. Преобразование объекта в этот формат — сериализация — позволяет сохранить всю информацию, необходимую для восстановления (или десериализации) объекта в нужное время.
Существует два основных способа сериализации объекта:
Персистенция объекта — это хранение состояния объекта в постоянном механизме персистенции, таком как база данных.
Дистанцирование (remoting) объекта означает передачу объекта в другой компьютер или систему.
В начало
java.io.Serializable
Первым шагом к выполнению работы по сериализации является предоставление объектам возможности использовать этот механизм. Каждый объект, который нужно сериализовать, должен реализовать интерфейс java.io.Serializable:
import java.io.Serializable;
public class Person implements Serializable // и т.д.
>Интерфейс Serializable помечает объекты класса Person для среды исполнения как сериализуемые. Каждый подкласс Person также помечается как сериализуемый.
При попытке сериализовать объект любые несериализуемые атрибуты объекта вызовут выдачу средой исполнения Java исключения NotSerializableException. Этим можно управлять с помощью ключевого слова transient, которое указывает среде времени выполнения, что пытаться сериализовать те или иные атрибуты не нужно. В этом случае сам программист несет ответственность за восстановление этих атрибутов для правильного функционирования объекта.
Сериализация объекта
Теперь рассмотрим пример, в котором сочетается все, что вы только что узнали об операциях ввода/вывода Java и о сериализации.
Предположим, что мы создали и заполнили объект Manager (напомним, что Manager находится в графе наследования Person, который является сериализуемым объектом), а теперь хотим сериализовать этот объект в выходной поток OutputStream, в данном случае, в файл. Этот процесс показан в листинге 29.Листинг 29. Сериализация объекта
Manager m = new Manager();
m.setEmployeeNumber(«0001»);m.setGender(Gender.FEMALE);
m.setAge(29);
m.setHeight(170);
m.setName(«Mary D. Boss»);
m.setTaxpayerIdentificationNumber(«123-45-6789»);
log.info(«About to write object using serialization. object looks like:»);
m.printAudit(log);
try String filename = «Manager-» + m.hashCode() + «.ser»;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
oos.writeObject(m);
log.info(«Wrote object. «);
> catch (Exception e) log.log(Level.SEVERE, «Caught Exception processing object», e);
>Первым шагом является создание объекта и установка некоторых значений атрибутов. Далее, создаем выходной поток OutputStream, в данном случае это FileOutputStream, а затем вызываем writeObject() для этого потока. writeObject () — это метод, который использует Java-сериализацию для преобразования объекта в поток.
В этом примере объект сохраняется в файле, но для любой сериализации используется одна и та же технология.
Десериализация объекта
Весь смысл сериализации объекта заключается в том, чтобы иметь возможность впоследствии восстановить, или десериализовать его. Листинг 30 считывает только что сериализованный файл и десериализует его содержание, тем самым восстанавливая состояние объекта Manager.Листинг 30. Десериализация объекта
Manager m = new Manager();
m.setEmployeeNumber(«0001»);
m.setGender(Gender.FEMALE);
m.setAge(29);
m.setHeight(170);
m.setName(«Mary D. Boss»);
m.setTaxpayerIdentificationNumber(«123-45-6789»);
log.info(«About to write object using serialization. object looks like:»);
m.printAudit(log);
try String filename = «Manager-» + m.hashCode() + «.ser»;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
oos.writeObject(m);
log.info(«Wrote object. «);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
m = (Manager)ois.readObject();
log.info(«Read object using serialization. object looks like:»);
m.printAudit(log);
> catch (Exception e) log.log(Level.SEVERE, «Caught Exception processing object», e);
>В большинстве случаев маркировка объектов как сериализуемых — это все, о чем вам когда-нибудь придется беспокоиться в связи с сериализацией. В тех случаях, когда нужно явно выполнять сериализацию и десериализацию объектов, можно использовать подход, показанный в листингах 29 и 30. Но по мере развития объектов вашего приложения и добавления/удаления их атрибутов сериализация приобретает новый уровень сложности.
В начало
Свойство serialVersionUID
В ранние дни промежуточного ПО и удаленной связи объектов разработчики в значительной мере сами отвечали за управление «форматом передачи» своих объектов, что вызывало бесконечные проблемы по мере развития технологии.
Предположим, что вы добавили атрибут к объекту, скомпилировали его и распространили код на все машины кластера приложения. Объект хранится на компьютере с одной версией кода сериализации, но доступен для других машин, которые могут иметь разные версии кода. Когда эти машины пытались десериализовать объект, часто происходили неприятные вещи.
Метаданные Java-сериализации – информация, включенная в двоичный формат сериализации, – имеют сложную структуру и решают многие проблемы, досаждающие ранним разработчикам промежуточного ПО. Но всех проблем они тоже не решают.
При Java-сериализации используется свойство serialVersionUID, которое помогает справиться с разными версиями объектов в сценарии сериализации. Вам не нужно декларировать это свойство для своих объектов; по умолчанию платформы Java использует алгоритм, который вычисляет его значение на основе атрибутов класса, его имени и положения в локальном кластере. В большинстве случаев это работает отлично. Но при добавлении или удалении атрибутов это динамически сгенерированное значение изменится, и среда исполнения Java выдаст исключение InvalidClassException.
Чтобы избежать этого, нужно взять в привычку явное объявление serialVersionUID:
import java.io.Serializable;
public class Person implements Serializable private static final long serialVersionUID = 20100515;
// и т.д.
>Я рекомендую использовать некую схему для номера версии serialVersionUID (в примере я использую текущую дату), причем нужно объявить его как private static final с типом long.
Вы можете спросить, когда изменять это свойство? Краткий ответ заключается в том, что его нужно изменять его всякий раз при внесении несовместимых изменений в класс, что обычно означает, что вы удалили атрибут. Если на компьютере имеется версия объекта, в которой атрибут удален, и этот объект переносится на машину с версией объекта, в которой этот атрибут ожидается, могут произойти неприятности.
В качестве золотого правила, при каждом добавлении или удалении элементов класса (то есть атрибутов и методов) нужно изменять его serialVersionUID. Лучше получить исключение InvalidClassException на другом конце «провода», чем приложение с ошибкой из-за несовместимых изменений класса.
