Перейти к основному содержимому

Что такое serialVersionUID?

· 5 мин. чтения

1. Обзор

Атрибут serialVersionUID — это идентификатор, который используется для сериализации/десериализации объекта класса Serializable .

В этом кратком руководстве мы обсудим, что такое serialVersionUID и как его использовать на примерах.

2. UID серийной версии

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

Атрибуты serialVersionUID разных классов независимы. Поэтому нет необходимости, чтобы разные классы имели уникальные значения.

Далее давайте узнаем, как использовать serialVersionUID на нескольких примерах.

Начнем с создания сериализуемого класса и объявления идентификатора serialVersionUID :

public class AppleProduct implements Serializable {

private static final long serialVersionUID = 1234567L;

public String headphonePort;
public String thunderboltPort;
}

Далее нам понадобятся два служебных класса: один для сериализации объекта AppleProduct в строку, а другой — для десериализации объекта из этой строки:

public class SerializationUtility {

public static void main(String[] args) {
AppleProduct macBook = new AppleProduct();
macBook.headphonePort = "headphonePort2020";
macBook.thunderboltPort = "thunderboltPort2020";

String serializedObj = serializeObjectToString(macBook);

System.out.println("Serialized AppleProduct object to string:");
System.out.println(serializedObj);
}

public static String serializeObjectToString(Serializable o) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
public class DeserializationUtility {

public static void main(String[] args) {

String serializedObj = ... // ommited for clarity
System.out.println(
"Deserializing AppleProduct...");

AppleProduct deserializedObj = (AppleProduct) deSerializeObjectFromString(
serializedObj);

System.out.println(
"Headphone port of AppleProduct:"
+ deserializedObj.getHeadphonePort());
System.out.println(
"Thunderbolt port of AppleProduct:"
+ deserializedObj.getThunderboltPort());
}

public static Object deSerializeObjectFromString(String s)
throws IOException, ClassNotFoundException {

byte[] data = Base64.getDecoder().decode(s);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(data));
Object o = ois.readObject();
ois.close();
return o;
}
}

Начнем с запуска SerializationUtility.java , который сохраняет (сериализует) объект AppleProduct в экземпляр String , кодируя байты с использованием Base64 .

Затем, используя эту строку в качестве аргумента для метода десериализации, мы запускаем DeserializationUtility.java, который повторно собирает (десериализует) объект AppleProduct из заданной строки.

Сгенерированный вывод должен быть похож на этот:

Serialized AppleProduct object to string:
rO0ABXNyACljb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkFwcGxlUHJvZHVjdAAAAAAAEta
HAgADTAANaGVhZHBob25lUG9ydHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADmxpZ2h0ZW5pbmdQb3
J0cQB+AAFMAA90aHVuZGVyYm9sdFBvcnRxAH4AAXhwdAARaGVhZHBob25lUG9ydDIwMjBwdAATd
Gh1bmRlcmJvbHRQb3J0MjAyMA==
Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020

Теперь давайте изменим константу serialVersionUID в AppleProduct.java и повторим попытку десериализации объекта AppleProduct из той же строки, созданной ранее. Повторный запуск DeserializationUtility.java должен сгенерировать этот вывод.

Deserializing AppleProduct...
Exception in thread "main" java.io.InvalidClassException: com.foreach.deserialization.AppleProduct; local class incompatible: stream classdesc serialVersionUID = 1234567, local class serialVersionUID = 7654321
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at com.foreach.deserialization.DeserializationUtility.deSerializeObjectFromString(DeserializationUtility.java:24)
at com.foreach.deserialization.DeserializationUtility.main(DeserializationUtility.java:15)

Изменив serialVersionUID класса, мы изменили его версию/состояние. В результате при десериализации не были найдены совместимые классы, и возникло исключение InvalidClassException .

Если serialVersionUID не указан в классе Serializable , JVM создаст его автоматически. Однако рекомендуется предоставлять значение serialVersionUID и обновлять его после внесения изменений в класс, чтобы мы могли контролировать процесс сериализации/десериализации . Мы рассмотрим его более подробно в следующем разделе.

3. Совместимые изменения

Допустим, нам нужно добавить новое поле LightningPort в наш существующий класс AppleProduct :

public class AppleProduct implements Serializable {
//...
public String lightningPort;
}

Поскольку мы просто добавляем новое поле, никаких изменений в serialVersionUID не потребуется . Это связано с тем, что в процессе десериализации в качестве значения по умолчанию для поля lightningPort будет назначено значение null `` .

Давайте изменим наш класс DeserializationUtility , чтобы напечатать значение этого нового поля:

System.out.println("LightningPort port of AppleProduct:"
+ deserializedObj.getLightningPort());

Теперь, когда мы повторно запустим класс DeserializationUtility , мы увидим вывод, подобный следующему:

Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020
Lightning port of AppleProduct:null

4. Серийная версия по умолчанию

Если мы не определим состояние serialVersionUID для класса Serializable , тогда Java определит его на основе некоторых свойств самого класса, таких как имя класса, поля экземпляра и т. д.

Давайте определим простой класс Serializable :

public class DefaultSerial implements Serializable {
}

Если мы сериализуем экземпляр этого класса следующим образом:

DefaultSerial instance = new DefaultSerial();
System.out.println(SerializationUtility.serializeObjectToString(instance));

Это напечатает дайджест Base64 сериализованного двоичного файла:

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

Как и раньше, мы должны иметь возможность десериализовать этот экземпляр из дайджеста:

String digest = "rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpY" 
+ "WxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw";
DefaultSerial instance = (DefaultSerial) DeserializationUtility.deSerializeObjectFromString(digest);

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

public class DefaultSerial implements Serializable {
private String name;
}

А затем попробуйте десериализовать тот же дайджест Base64 в экземпляр класса, мы получим InvalidClassException:

Exception in thread "main" java.io.InvalidClassException: 
com.foreach.deserialization.DefaultSerial; local class incompatible:
stream classdesc serialVersionUID = 9045863543269746292,
local class serialVersionUID = -2692722436255640434

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

5. Вывод

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

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