Serialization is a means of object persistence. It is widely used in network transmission and RMI scenarios. Class implements the java.io.Serializable interface to enable its serialization capabilities.

Serialization has already been covered in several articles on my blog. If you are not familiar with the basics of serialization, you can refer to the following articles:

Serialization and deserialization of Java Objects take a closer look at Java serialization and deserialization singletons and those things that serialize

In these articles, I have introduced the classes and interfaces involved in serialization, how to customize the serialization strategy, transient keywords and the relationship between serialization, etc. I have also studied serialization in depth by learning the source code of ArrayList to implement serialization. Furthermore, the influence of serialization on singleton is analyzed.

However, there is one point that has not been covered, and that is the serialVersionUID. What exactly does this field do? What if you don’t set it? Why there are the following provisions in Alibaba Java Development Manual:


Before I expand on this article, let’s take a quick look at serialization from the three article links at the beginning of this article.

The Serializable and Externalizable

The Java class enables its serialization functionality by implementing the java.io.Serializable interface. Classes that do not implement this interface will not be serialized or deserialized. All subtypes of a serializable class are themselves serializable.

If you look at the source code for Serializable, you’ll see that it’s just an empty interface with nothing in it. The Serializable interface has no methods or fields and is only used to identify Serializable semantics. However, if a class does not implement this interface, want to be serialized, it throws an Java IO. NotSerializableException anomalies.

How does it ensure that only methods that implement the interface can be serialized and deserialized?

The reason is that during serialization, the following code is executed:

if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<? >) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); }}Copy the code

When serializing, it determines whether the class to be serialized is an Enum, Array, or Serializable type, and throws A NotSerializableException if it is not.

The Externalizable interface is also provided in Java and can also be implemented to provide serialization capabilities.

Externalizable inherits from Serializable, where two abstract methods are defined: writeExternal() and readExternal().

When using the Externalizable interface for serialization and deserialization, developers need to override the writeExternal() and readExternal() methods. Otherwise, all variable values are changed to default values.

transient

The function of the TRANSIENT keyword is to control the serialization of variables. Adding the keyword before the variable declaration can prevent the variable from being serialized to the file. After deserialization, the value of the transient variable is set to its initial value, such as 0 for int and NULL for object.

Customize the serialization policy

During the serialization process, if writeObject and readObject methods are defined in the serialized class, the VM attempts to invoke the writeObject and readObject methods in the object class to implement user-defined serialization and deserialization.

If there is no such method, the default calls are the defaultWriteObject method of ObjectOutputStream and the defaultReadObject method of ObjectInputStream.

User-defined writeObject and readObject methods allow the user to control the serialization process, such as dynamically changing the value of the serialization during the serialization process.

So, when you need to define a serialization strategy for special fields, consider using the transient modifier and overriding the writeObject and readObject methods yourself, as is done in java.util.arrayList.

That’s all you need to know about serialization.

If we look at a few random Java classes that implement serialization interfaces, such as String, Integer, etc., we can find a detail that in addition to implementing Serializable, these classes also define a serialVersionUID

So what exactly is a serialVersionUID? Why set such a field?


Serialization is the process of converting the state information of an object into a form that can be stored or transmitted. As we all know, Java objects are stored in the JVM’s heap memory, which means that if the JVM heap no longer exists, the objects disappear with it.

Serialization provides a solution that allows you to save objects even when the JVM is down. Just like the usb flash drive we use. Serialize Java objects into a form (such as a binary stream) that can be stored or transferred, such as in a file. This way, when the object is needed again, the binary stream is read from the file and the object is deserialized from the binary stream.

Whether the virtual machine allows deserialization depends not only on the consistency of the classpath and the functionality code, but also on the consistency of the serialization ID of the two classes. This serialization ID is the serialVersionUID defined in the code.


Let’s take an example and see what happens if the serialVersionUID is changed.

public class SerializableDemo1 { public static void main(String[] args) { //Initializes The Object User1 user = new User1(); user.setName("hollis"); //Write Obj to File ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(oos); } }}class User1 implements Serializable { private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}Copy the code

Let’s start by executing the code above and writing a User1 object to the file. Then we modify the User1 class to change the value of serialVersionUID to 2L.

class User1 implements Serializable {    private static final long serialVersionUID = 2L;    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}Copy the code

Then execute the following code to deserialize the objects in the file:

public class SerializableDemo2 { public static void main(String[] args) { //Read Obj from File File file = new File("tempFile"); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(file)); User1 newUser = (User1) ois.readObject(); System.out.println(newUser); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(ois); try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

The result is as follows:

java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2Copy the code

Can be found that the above code throws a Java IO. InvalidClassException, and points out that the serialVersionUID.

This is because, during deserialization, the JVM will compare the serialVersionUID in the transmitted byte stream with the serialVersionUID of the corresponding local entity class. If they are the same, the serialVersionUID is considered the same and can be deserialized. Otherwise, the serialVersionUID will be inconsistent. Is InvalidCastException.

This is also the reason why the “Alibaba Java Development Manual” stipulates that the serialVersionUID should not be modified when the class is modified in the compatibility upgrade. Unless the two versions are completely incompatible. So, the serialVersionUID actually verifies version consistency.

If you are interested, take a look at each version of the JDK code. The serialVersionUID of any backward compatible class is unchanged. For example, the serialVersionUID of the String class is always -6849794470754667710L.

However, the author believes that the specification can be more strict, that is, the provisions:

If a class implements the Serializable interface, you must manually add a private Static Final Long serialVersionUID variable and set its initial value.


See what happens if we don’t explicitly define a serialVersionUID in our class.

Try modifying the demo code above by defining an object with the following class that does not define the serialVersionUID and writing it to a file.

class User1 implements Serializable { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}Copy the code

Then we modify the User1 class to add a property to it. Try to read it out of the file and deserialize it.

class User1 implements Serializable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}Copy the code

Execution Result:

java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402Copy the code

Also, an InvalidClassException is thrown and it is noted that the two serialVersionUID are different, -2986778152837257883 and 7961728318907695402.

From here, you can see that the system itself has added a serialVersionUID.

Therefore, once a class implements Serializable, it is recommended to explicitly define a serialVersionUID. Otherwise, an exception will occur when you modify the class.

SerialVersionUID has two ways to generate the display: one is the default 1L, for example:

private static final long serialVersionUID = 1L;   Copy the code

The other option is to generate a 64-bit hash field based on the class name, interface name, member methods, attributes, etc., for example:

private static final  long   serialVersionUID = xxxxL;Copy the code

The latter method, which can be generated using an IDE, will be described later.


Know why, to know why, let’s take a look at the source code, analyze why serialVersionUID changes when throwing exceptions? Where does the default serialVersionUID come from in the absence of an explicit definition?

To simplify the code, the deserialized call chain is as follows:

ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxyCopy the code

In initNonProxy, the key code is as follows:

During deserialization, the serialVersionUID is compared, and if it is not equal, an exception is thrown directly.

Take a closer look at the getSerialVersionUID method:

public long getSerialVersionUID() { // REMIND: synchronize instead of relying on volatile? if (suid == null) { suid = AccessController.doPrivileged( new PrivilegedAction<Long>() { public Long run() { return computeDefaultSUID(cl); }}); } return suid.longValue(); }Copy the code

When serialVersionUID is not defined, the computeDefaultSUID method is called to generate a default serialVersionUID.

This leads to the root cause of the above two problems, which is that strict validation is done in the code and a serialVersionUID is automatically generated when undefined.


To make sure we don’t forget to define the serialVersionUID, we can adjust the configuration of Intellij IDEA. After implementing the Serializable interface, if the serialVersionUID is not defined, IDEA (like Eclipse) will tell you:


And can generate one with one key:

Of course, this configuration does not take effect by default, you need to manually set the IDEA:

In the box marked 3 (Serializable Class without serialVersionUID configuration), check and save.


SerialVersionUID is used to verify version consistency. Do not change the value of serialVersionUID in the class when doing compatibility upgrades.

Note that serialVersionUID is not a complete description of this article. It is important to change the value of the serialVersionUID field to avoid serialization confusion.

If a class implements the Serializable interface, be sure to define serialVersionUID otherwise an exception will occur. You can set it in the IDE to help prompt and quickly generate a serialVersionUID with one click.

The exception occurs because deserialization is validated and, if not explicitly defined, generates one automatically based on class names, attributes, and so on.

Hollis public account has authorized “Knight for Rights protection” to carry out original rights protection, in order to avoid unnecessary copyright liability issues, please indicate the source of reprint!

In the last month of 2018, Hollis knowledge Planet is on sale for a limited time. In-depth understanding of Concurrent programming in Java: What is Thread-safe, anyway? Welcome to join us.

Why is garbled?

Learn more about IO in Java

Deep concurrency Issue 004: Multiple ways to implement threads

– MORE | – MORE excellent articles

  • A few suggestions about winter

  • One production CPU 100% check optimization practice

  • Gossip: How do you explain load balancing to your girlfriend

  • Summary of some commonly used algorithm skills

If you enjoyed this article.

Please long press the QR code to follow Hollis

Forwarding moments is the biggest support for me.