Cos'è serialVersionUID?

1. Panoramica

In poche parole, il serialVersionUID è un identificatore univoco per Serializable classi.

Viene utilizzato durante la deserializzazione di un oggetto, per garantire che una classe caricata sia compatibile con l'oggetto serializzato. Se non viene trovata alcuna classe corrispondente, viene generata un'eccezione InvalidClassException .

2. UID versione seriale

Iniziamo creando una classe serializzabile e dichiariamo un identificatore serialVersionUID :

public class AppleProduct implements Serializable { private static final long serialVersionUID = 1234567L; public String headphonePort; public String thunderboltPort; }

Successivamente, avremo bisogno di due classi di utilità: una per serializzare un oggetto AppleProduct in una stringa e un'altra per deserializzare l'oggetto da quella stringa:

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; } }

Iniziamo eseguendo SerializationUtility.java , che salva (serializza) l' oggetto AppleProduct in un'istanza String , codificando i byte utilizzando Base64.

Quindi, utilizzando quella stringa come argomento per il metodo di deserializzazione, eseguiamo DeserializationUtility.java, che riassembla (deserializza) l' oggetto AppleProduct dalla stringa data .

L'output generato dovrebbe essere simile a questo:

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

Ora, modifichiamo la costante serialVersionUID in AppleProduct.java e riproviamo a deserializzare l' oggetto AppleProduct dalla stessa stringa prodotta in precedenza. La riesecuzione di DeserializationUtility.java dovrebbe generare questo output.

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)

Modificando il serialVersionUID della classe, abbiamo modificato la sua versione / stato. Di conseguenza, durante la deserializzazione non sono state trovate classi compatibili ed è stata generata un'eccezione InvalidClassException .

3. Modifiche compatibili

Supponiamo di dover aggiungere un nuovo campo lightningPort alla nostra classe AppleProduct esistente :

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

Dato che stiamo solo aggiungendo un nuovo campo, non sarà richiesta alcuna modifica in serialVersionUID . Questo perché, durante il processo di deserializzazione, null verrà assegnato come valore predefinito per il campo lightningPort .

Modifichiamo la nostra classe DeserializationUtility per stampare il valore di questo nuovo campo:

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

Ora, quando eseguiamo nuovamente la classe DeserializationUtility , vedremo un output simile a:

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

4. Versione seriale predefinita

Se non definiamo uno stato serialVersionUID per una classe Serializable , Java ne definirà uno in base ad alcune proprietà della classe stessa come il nome della classe, i campi dell'istanza e così via.

Definiamo una semplice classe Serializable :

public class DefaultSerial implements Serializable { }

Se serializziamo un'istanza di questa classe come la seguente:

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

Questo stamperà il digest Base64 del binario serializzato:

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

Proprio come prima, dovremmo essere in grado di deserializzare questa istanza dal digest:

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

Tuttavia, alcune modifiche a questa classe potrebbero interrompere la compatibilità della serializzazione. Ad esempio, se aggiungiamo un campo privato a questa classe:

public class DefaultSerial implements Serializable { private String name; }

E poi prova a deserializzare lo stesso digest Base64 in un'istanza di classe, otterremo un'eccezione InvalidClassException:

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

A causa di questo tipo di incompatibilità indesiderata, è sempre una buona idea dichiarare un serialVersionUID nelle classi Serializable . In questo modo possiamo mantenere o evolvere la versione man mano che la classe stessa si evolve.

5. conclusione

In questo rapido articolo, abbiamo dimostrato l'uso della costante serialVersionUID per facilitare il controllo delle versioni dei dati serializzati.

Come sempre, gli esempi di codice utilizzati in questo articolo possono essere trovati su GitHub.