¿Qué es serialVersionUID?

1. Información general

En pocas palabras, el serialVersionUID es un identificador único para Serializable clases.

Esto se utiliza durante la deserialización de un objeto, para garantizar que una clase cargada sea compatible con el objeto serializado. Si no se encuentra una clase coincidente, se lanza una InvalidClassException .

2. UID de la versión de serie

Comencemos por crear una clase serializable y declarar un identificador serialVersionUID :

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

A continuación, necesitaremos dos clases de utilidad: una para serializar un objeto AppleProduct en una cadena y otra para deserializar el objeto de esa cadena:

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

Comenzamos ejecutando SerializationUtility.java , que guarda (serializa) el objeto AppleProduct en una instancia de String , codificando los bytes usando Base64.

Luego, usando esa Cadena como argumento para el método de deserialización, ejecutamos DeserializationUtility.java, que reensambla (deserializa) el objeto AppleProduct de la Cadena dada .

La salida generada debería ser similar a esta:

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

Ahora, modifiquemos la constante serialVersionUID en AppleProduct.java y vuelva a intentar deserializar el objeto AppleProduct de la misma cadena producida anteriormente. Volver a ejecutar DeserializationUtility.java debería generar esta salida.

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)

Al cambiar el serialVersionUID de la clase, modificamos su versión / estado. Como resultado, no se encontraron clases compatibles durante la deserialización y se lanzó una InvalidClassException .

3. Cambios compatibles

Digamos que necesitamos agregar un nuevo campo lightningPort a nuestra clase AppleProduct existente :

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

Dado que solo estamos agregando un nuevo campo, no se requerirá ningún cambio en el serialVersionUID . Esto se debe a que, durante el proceso de deserialización, se asignará null como valor predeterminado para el campo lightningPort .

Modifiquemos nuestra clase DeserializationUtility para imprimir el valor de este nuevo campo:

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

Ahora, cuando volvamos a ejecutar la clase DeserializationUtility , veremos un resultado similar a:

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

4. Versión de serie predeterminada

Si no definimos un estado serialVersionUID para una clase serializable , Java definirá uno basado en algunas propiedades de la propia clase, como el nombre de la clase, los campos de instancia, etc.

Definamos una clase serializable simple :

public class DefaultSerial implements Serializable { }

Si serializamos una instancia de esta clase como la siguiente:

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

Esto imprimirá el resumen Base64 del binario serializado:

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

Al igual que antes, deberíamos poder deserializar esta instancia del resumen:

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

Sin embargo, algunos cambios en esta clase pueden romper la compatibilidad de serialización. Por ejemplo, si agregamos un campo privado a esta clase:

public class DefaultSerial implements Serializable { private String name; }

Y luego intente deserializar el mismo resumen de Base64 en una instancia de clase, obtendremos una InvalidClassException:

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

Debido a este tipo de incompatibilidad no deseada, siempre es una buena idea declarar un serialVersionUID en las clases serializables . De esta manera podemos mantener o evolucionar la versión a medida que evoluciona la clase.

5. Conclusión

En este artículo rápido, demostramos el uso de la constante serialVersionUID para facilitar el control de versiones de datos serializados.

Como siempre, los ejemplos de código utilizados a lo largo de este artículo se pueden encontrar en GitHub.