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 .