Руководство по Java SerialVersionUID

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

Во время сериализации среда выполнения Java связывает номер версии с каждым сериализуемым классом. Этот номер называется serialVersionUID и используется во время десериализации для проверки того, что отправитель и получатель сериализованного объекта загрузили классы для этого объекта, совместимые с точки зрения сериализации. Если получатель загрузил класс для объекта, который имеет serialVersionUID, отличный от соответствующего класса отправителя, то десериализация приведет к исключению InvalidClassException.

1. Синтаксис Java SerialVersionUID

Класс Serializable может явно объявить свой собственный SerialVersionUID, объявив поле с именем «serialVersionUID», которое должно быть статическим, окончательным и иметь тип long.

private static final long serialVersionUID = 1L;

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

Сериализация-десериализация-демо

2. Примеры сериализации и десериализации Java

Давайте рассмотрим пример того, как класс сериализуется, а затем десериализуется.

import java.io.*;import java.util.logging.Logger;public class DemoClass implements java.io.Serializable {private static final long serialVersionUID = 4L; //Default serial version uidprivate static final String fileName = "DemoClassBytes.ser"; //Any random nameprivate static final Logger logger = Logger.getLogger("");//Few data fields//Able to serializeprivate static String staticVariable;private int intVariable;//Not able to serializetransient private String transientVariable = "this is a transient instance field";private Thread threadClass;public static void main(String[] args) throws IOException, ClassNotFoundException{//SerializationDemoClass test = new DemoClass();test.intVariable = 1;staticVariable = "this is a static variable";writeOut(test);System.out.println("DemoClass to be saved: " + test);//De-serializationSystem.out.println("DemoClass deserialized: " + readIn());}private static Object readIn() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)));return ois.readObject();}private static void writeOut(java.io.Serializable obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName)));oos.writeObject(obj);oos.close();}@Override public String toString() {return "DemoClass: final static fileName=" + fileName + ", final static logger=" + logger+ ", non-final static staticVariable=" + staticVariable + ", instance intVariable=" + intVariable+ ", transient instance transientVariable=" + transientVariable + ", non-serializable instance field threadClass:=" + threadClass;}}

Вывод программы.

DemoClass to be saved: DemoClass:final static fileName=DemoClassBytes.ser,final static logger=java.util.logging.LogManager$RootLogger@1d99a4d,non-final static staticVariable=this is a static variable,instance intVariable=1,transient instance transientVariable=this is a transient instance field,non-serializable instance field threadClass:=null//Execute readIn() function from a separate main() method//to get given below output correctly. It will flush out the static fields.DemoClass deserialized: DemoClass:final static fileName=DemoClassBytes.ser,final static logger=java.util.logging.LogManager$RootLogger@cd2c3c,non-final static staticVariable=null,instance intVariable=1,transient instance transientVariable=null,non-serializable instance field threadClass:=null

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

3. Как сгенерировать SerialVersionUID

Джошуа Блох в книге Effective Java говорит, что автоматически сгенерированный UID генерируется на основе имени класса, реализованных интерфейсов и всех открытых и защищенных членов. Изменение любого из них каким-либо образом изменит serialVersionUID.

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

Поэтому, чтобы гарантировать согласованное значение serialVersionUID в различных реализациях компилятора Java, сериализуемый класс должен объявить явное значение serialVersionUID. Также настоятельно рекомендуется, чтобы явные объявления serialVersionUID использовали модификатор private в serialVersionUID, где это возможно, поскольку такие объявления применяются только к немедленно объявляющему классу.

Также обратите внимание, что поле serialVersionUID бесполезно в качестве унаследованного члена.

На основе моей короткой карьеры я могу сказать, что хранение сериализованных данных в течение длительного периода времени [пространственная сериализация] не является очень распространенным вариантом использования. Гораздо более распространено использование механизма сериализации для временной записи данных [временная сериализация], например, в кэш или отправки их по сети в другую программу для использования информации.

В таких случаях мы не заинтересованы в поддержании обратной совместимости. Мы только заботимся о том, чтобы убедиться, что кодовые базы, которые взаимодействуют по сети, действительно имеют одну и ту же версию соответствующих классов. Чтобы облегчить такую проверку, мы должны поддерживать serialVersionUID таким, какой он есть, и не изменять его. Кроме того, не забывайте обновлять его при внесении несовместимых изменений в ваши классы, на обеих сторонах приложений в сети.

4. Классы Java без SerialVersionUID

Это не та ситуация, с которой мы когда-либо хотели бы столкнуться. Но это реальность, и иногда это случается(должен ли я сказать редко?). Если нам нужно изменить такой класс несовместимым образом, но мы хотим сохранить возможность сериализации/десериализации со старой версией класса, мы можем использовать инструмент JDK « serialver ». Этот инструмент генерирует serialVersionUID для старого класса и явно устанавливает его для нового класса. Не забудьте реализовать методы readObject() и writeObject(), потому что встроенный механизм десериализации(in.defaultReadObject()) откажется десериализоваться из старых версий данных.

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

Подробнее: Совместимые и несовместимые изменения сериализации Java

5. Резюме

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

    Поля final и static по-прежнему имеют значения, поскольку они являются частью данных класса.

  • ObjectOutputStream.writeObject(obj) и ObjectInputStream.readObject() используются при сериализации и десериализации.
  • Во время сериализации нам нужно обработать IOException; во время десериализации нам нужно обработать IOException и ClassNotFoundException. Поэтому тип десериализованного класса должен быть в classpath.
  • Допускаются неинициализированные, несериализуемые, непереходные поля экземпляра.

    При добавлении «private Thread th;» нет ошибки в serializable. Однако «private Thread threadClass = new Thread();» вызовет исключение:

Exception in thread "main" java.io.NotSerializableException: java.lang.Threadat java.io.ObjectOutputStream.writeObject0(Unknown Source)at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)at java.io.ObjectOutputStream.writeSerialData(Unknown Source)at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)at java.io.ObjectOutputStream.writeObject0(Unknown Source)at java.io.ObjectOutputStream.writeObject(Unknown Source)at com.howtodoinjava.demo.serialization.DemoClass.writeOut(DemoClass.java:42)at com.howtodoinjava.demo.serialization.DemoClass.main(DemoClass.java:27)
  • Сериализация и десериализация могут использоваться для копирования и клонирования объектов. Это медленнее, чем обычное клонирование, но может производить глубокую копию очень быстро.
  • Если мне нужно сериализовать Serializable класс Employee, но один из его суперклассов не Serializable, можно ли все равно сериализовать и десериализовать класс Employee? Ответ — да, при условии, что несериализуемый суперкласс имеет конструктор без аргументов, который вызывается при десериализации для инициализации этого суперкласса.
  • Мы должны быть осторожны при изменении класса и реализации java.io.Serializable. Если класс не содержит поля serialVersionUID, его serialVersionUID будет автоматически сгенерирован компилятором.

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

  • Вычисление serialVersionUID основано не только на полях, но и на других аспектах класса, таких как предложения реализации, конструкторы и т. д. Поэтому лучшей практикой является явное объявление поля serialVersionUID для поддержания обратной совместимости. Если нам нужно существенно изменить сериализуемый класс и мы ожидаем, что он будет несовместим с предыдущими версиями, то нам нужно увеличить serialVersionUID, чтобы избежать смешивания разных версий.

Прокрутить вверх