Какво представлява serialVersionUID?

1. Общ преглед

Казано по-просто, по serialVersionUID е уникален идентификатор за Serializable класове.

Това се използва по време на десериализацията на обект, за да се гарантира, че зареденият клас е съвместим със сериализирания обект. Ако не бъде намерен съвпадащ клас, се хвърля InvalidClassException .

2. UID на серийната версия

Нека започнем със създаването на сериализуем клас и декларираме идентификатор 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 instanc e, кодирайки байтовете с помощта на Base64.

След това, използвайки този String като аргумент за метода на десериализация, стартираме DeserializationUtility.java, който отново сглобява (десериализира) обекта AppleProduct от дадения String.

Генерираната продукция трябва да бъде подобна на тази:

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 обект от същия String произвежда по-рано. Повторното стартиране на DeserializationUtility.java трябва да генерира този изход.

Deserializing AppleProduct... Exception in thread "main" java.io.InvalidClassException: com.baeldung.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.baeldung.deserialization.DeserializationUtility.deSerializeObjectFromString(DeserializationUtility.java:24) at com.baeldung.deserialization.DeserializationUtility.main(DeserializationUtility.java:15)

Чрез промяна на serialVersionUID на класа променихме неговата версия / състояние. В резултат на това по време на десериализацията не бяха намерени съвместими класове и беше хвърлено InvalidClassException .

3. Съвместими промени

Да предположим, че трябва да добавим нов полев lightningPort към съществуващия ни клас AppleProduct :

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

Тъй като просто добавяме ново поле, няма да е необходима промяна в serialVersionUID . Това е така, защото по време на процеса на deserialization, нула ще бъде назначен като стойността по подразбиране за lightningPort областта .

Нека модифицираме нашия клас 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 за сериализуем клас, тогава Java ще дефинира такова въз основа на някои свойства на самия клас като име на класа, полета на екземпляра и т.н.

Нека дефинираме прост сериализуем клас:

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.baeldung.deserialization.DefaultSerial; local class incompatible: stream classdesc serialVersionUID = 9045863543269746292, local class serialVersionUID = -2692722436255640434

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

5. Заключение

В тази бърза статия демонстрирахме използването на константата serialVersionUID за улесняване на версирането на сериализирани данни.

Както винаги, примерните кодове, използвани в тази статия, могат да бъдат намерени в GitHub.