What is serialVersionUID? Detailed explanation of serialVersionUID

When reading many open source projects, you will find that there is a quantity modified with static final in many classes, which is usually very large and does not seem to have any rules with the code.
For example, the following code:

public class Dml implements Serializable {
    
    
     private static final long erialVersionUID = 2611556444074013268L;

(Code comes from Alibaba open source project Client Adapter)

Overview

The serialVersionUID attribute is the identifier used to serialize/deserialize the object serialization class.

The serialization runtime associates a version number, serialVersionUIDcalled Compatible classes.

serialVersionUIDDeserialization will result if the class loaded by the receiver for the object is different from the corresponding sender's class InvalidClassException. A serializable class can explicitly declare itself serialVersionUIDby declaring a field that must be static, final, and type long:serialVersionUID

private static final long serialVersionUID = 42L;

If a serializable class does not explicitly declare one serialVersionUID, the serialization runtime will serialVersionUIDcalculate a default value for the class based on aspects of the class, as described in the Java Object Serialization Specification.

However, it is strongly recommended that all serializable classes explicitly declare the serialVersionUIDvalue, as the default serialVersionUIDcalculation is highly sensitive to class details, which may vary depending on the compiler implementation and therefore may InvalidClassExceptionslead to surprises during deserialization Condition.

Therefore, in order to guarantee serialVersionUIDa consistent value between different Java compiler implementations, a serializable class must declare an explicit serialVersionUIDvalue. It is also strongly recommended that explicit serialVersionUIDdeclarations use the private modifier whenever possible, since such declarations only apply to the immediately declared class - serialVersionUIDfields are of no use as inherited members.

Serial number UID

In short, we use the serialVersionUID attribute to remember the version of the Serializable class to verify that the loaded class and the serialized object are compatible.

The serialVersionUID properties of different classes are independent. Therefore, different classes do not have to have unique values.

Next, let's learn how to use serialVersionUID with some examples.

First create a serializable class and declare a serialVersionUID identifier:

//代码来自baeldung
public class AppleProduct implements Serializable {
    
    
    private static final long serialVersionUID = 1234567L;
    public String headphonePort;
    public String thunderboltPort;
}

Next, we will need two utility classes: one to serialize the AppleProduct object to a String, and one to deserialize the object from the String:

//代码来自baeldung
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());
    }
}
//代码来自baeldung
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;
    }
}

We start by running SerializationUtility.java, which saves (serializes) the AppleProduct object as a String instance and encodes the bytes using Base64.

Then, using that String as an argument to the Deserialization method, we run DeserializationUtility.java, which reassembles (deserializes) the AppleProduct object from the given String.

The generated output should be similar to this:

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

Now, let us modify the serialVersionUID constant in AppleProduct.java and retry deserializing the AppleProduct object from the same String produced earlier. Rerunning DeserializationUtility.java should generate this output.

//代码来自baeldung
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)

By changing the serialVersionUID of a class, we modify its version/state. As a result, no compatible class was found during deserialization and an InvalidClassException was thrown.

If serialVersionUID is not provided in the Serializable class, the JVM will automatically generate one. However, it is good practice to provide the serialVersionUID value and update it after changing the class so that we can control the serialization/deserialization process. We'll take a closer look at it in the next section.

Compatible changes

Let's say we need to add a new lightningPort field to an existing AppleProduct class:

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

Since we are just adding a new field, no changes in the serialVersionUID will be needed. This is because, during deserialization, null is assigned as the default value for the lightningPort field.

Let's modify the DeserializationUtility class to print the value of this new field:

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

Now, when we rerun the DeserializationUtility class, we will see output similar to the following:

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

Default serial version

If we do not define the serialVersionUID state for the Serializable class, Java will define one based on some properties of the class itself (e.g., class name, instance fields, etc.).

Let's define a simple Serializable class:

public class DefaultSerial implements Serializable {
    
    
}

If we serialize an instance of this class like this:

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

This will print the Base64 digest of the serialized binary:

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

As before, we should be able to deserialize this instance from the digest:

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

However, some changes to this class may break serialization compatibility. For example, if we add a private field to this class:

public class DefaultSerial implements Serializable {
    
    
    private String name;
}

Then trying to deserialize the same Base64 digest into a class instance, we get an InvalidClassException:

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

Due to this unnecessary incompatibility, it is always a good idea to declare serialVersionUID in the Serializable class. This way we can keep or evolve versions as the class itself evolves.

in conclusion

In this quick article, we demonstrate how to use the serialVersionUID constant to simplify versioning of serialized data.

Part of the content is translated from Java Doc and baeldung

Guess you like

Origin blog.csdn.net/qq_20051535/article/details/113615657