How to use serialVersionUID in java learning

About serialVersionUID. What exactly is this field for? What happens if you don't set it? Why does the "Alibaba Java Development Manual" have the following provisions:

![-w934][4]

background knowledge

Serializable 和 Externalizable

A class implements java.io.Serializablethe interface to enable its serialization capabilities. **Classes that do not implement this interface will not be able to be serialized or deserialized. ** All subtypes of a serializable class are themselves serializable.

If readers have read Serializablethe source code, they will find that it is just an empty interface with nothing in it. **Serializable interface has no methods or fields, and is only used to identify serializable semantics. **However, if a class does not implement this interface and wants to be serialized, java.io.NotSerializableExceptionan exception will be thrown.

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

The reason is that during the serialization process, the following code will be 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());
    }
}

When performing a serialization operation, it will be judged whether the class to be serialized is Enum, Arrayand Serializabletype, and if it is not, it will be thrown directly NotSerializableException.

Interfaces are also provided in Java Externalizable, which can also be implemented to provide serialization capabilities.

ExternalizableInherited from Serializable, two abstract methods are defined in this interface: writeExternal()and readExternal().

When using Externalizablethe interface for serialization and deserialization, developers need to override writeExternal()and readExternal()method. Otherwise, the values ​​of all variables will become default values.

transient

transientThe function of the keyword is to control the serialization of the variable. Adding this keyword before the variable declaration can prevent the variable from being serialized into the file. After being deserialized, the value of the variable is set to the initial value, such as transientint type is 0, object type is null.

custom serialization strategy

writeObjectDuring the serialization process, if the and methods are defined in the serialized class , the virtual machine tries to call the and methods readObjectin the object class to perform user-defined serialization and deserialization.writeObjectreadObject

If there is no such method, the default calls are the method ObjectOutputStreamof defaultWriteObjectand the method ObjectInputStreamof defaultReadObject.

User-defined writeObjectand readObjectmethods allow users to control the serialization process, such as dynamically changing the serialized value during the serialization process.

Therefore, when you need to define a serialization strategy for some special fields, you can consider using transient modification and rewriting the writeObjectand readObjectmethods yourself, such as java.util.ArrayListthe implementation in .

We randomly find a few classes in Java that implement serialization interfaces, such as String, Integer, etc., and we can find a detail, that is, in addition to implementing these classes, they also define a ![][5 Serializable] serialVersionUID

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

What is serialVersionUID

Serialization is the process of converting an object's state information into a form that can be stored or transmitted. We all know that Java objects are stored in the heap memory of the JVM, that is to say, if the JVM heap does not exist, then the object will disappear.

Serialization provides a solution that allows you to save objects even if the JVM is down. Just like the U disk we usually use. Serialize Java objects into a form that can be stored or transmitted (such as a binary stream), such as saving in a file. In 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 whether the classpath and function code are consistent, but also on whether the serialization IDs of the two classes are consistent. This so-called serialization ID is defined in the code serialVersionUID.

What happens if the serialVersionUID changes

Let's take an example and see serialVersionUIDwhat happens if it is modified?

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

We first execute the above code and write a User1 object to the file. Then we modify the User1 class and serialVersionUIDchange the value of 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;
    }
}

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

The execution results are as follows:

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

It can be found that the above code throws a java.io.InvalidClassExceptionand points out serialVersionUIDthe inconsistency.

This is because, when deserializing, the JVM will compare the incoming byte stream serialVersionUIDwith the corresponding local entity class serialVersionUID. If they are the same, it is considered consistent and can be deserialized, otherwise there will be a sequence Exceptions for version inconsistencies, that is InvalidCastException.

This is why it is stipulated in the "Alibaba Java Development Manual" that during compatibility upgrades, when modifying classes, do not modify them serialVersionUID. Unless it's two versions that are completely incompatible . Therefore, serialVersionUIDit is actually verifying version consistency.

If readers are interested, you can take a look at the JDK codes of each version. Those backward compatible classes have serialVersionUIDnot changed. For example, the String class serialVersionUIDhas always been -6849794470754667710L.

However, the author believes that this specification can actually be stricter, that is, the regulation:

If a class implements Serializablethe interface, a variable must be manually added private static final long serialVersionUIDand the initial value set.

Why do you need to explicitly specify a serialVersionUID

serialVersionUIDSee what happens if we don't explicitly define one in the class .

Try to modify the demo code above, first use the following class to define an object, which is not defined in this class serialVersionUID, and write it to the file.

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

Then we modify the User1 class and add a property to it. Trying to read it from 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;
    }
 }

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

Again, throws out InvalidClassExceptionand points out two serialVersionUIDdifferences, which are -2986778152837257883and 7961728318907695402.

It can be seen from here that the system has added one by itself serialVersionUID.

So, once the class is implemented Serializable, it is recommended to define one explicitly serialVersionUID. Otherwise, when modifying the class, an exception will occur.

serialVersionUIDThere are two display generation methods:
one is the default 1L, for example: private static final long serialVersionUID = 1L;
the other is to generate a 64-bit hash field according to the class name, interface name, member method and attribute, etc., for example:
private static final long serialVersionUID = xxxxL;

The latter method can be generated with the help of IDE, which will be introduced later.

Principle behind

To know why, let’s take a look at the source code and analyze why serialVersionUIDan exception is thrown when changing? serialVersionUIDWhere does the default come from when there is no explicit definition ?

In order to simplify the amount of code, the call chain for deserialization is as follows:

ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy

In initNonProxy, the key code is as follows:

![][6]

During the deserialization process, serialVersionUIDcomparisons are made, and if they are found to be unequal, an exception is thrown directly.

Take a deeper look at getSerialVersionUIDthe 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();
}

When not defined serialVersionUID, computeDefaultSUIDthe method will be called to generate a default one serialVersionUID.

This also finds the root of the above two problems. In fact, strict verification has been done in the code.

IDEA tips

In order to ensure that we will not forget the definition serialVersionUID, we can adjust the configuration of Intellij IDEA. After implementing Serializablethe interface, if there is no definition serialVersionUID, IDEA (same as eclipse) will prompt: ![][7]

And you can generate one with one click:

![][8]

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

![][9]

In the place labeled 3 in the figure (Serializable class without serialVersionUID configuration), tick it and save it.

Summarize

serialVersionUIDIt is used to verify version consistency. So when doing compatibility upgrades, don't change serialVersionUIDthe value in the class.

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

The reason why the exception occurs is because the verification is done during the deserialization process, and if it is not clearly defined, one will be automatically generated according to the properties of the class.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132307283