Java中的SerialVersionUID用于Java中的序列化机制,在Java的序列化中,尤其是在使用Eclipse这个IDE的时候,总是会warning说需要给类一个SerialVersionUID,换到IDEA的时候,在创建新类的时候不会warning需要SerialVersionUID的。在看其他的代码中,很容易看到以下的代码:

    private static final long serialVersionUID = 6151094464866251207L;
    private static final long serialVersionUID = 1L;     

这里产生了几个问题:

1. 那么后面的这一长串的值是什么?

2. 不写对序列化有什么影响?

3. 保持SerialVersionUID不写,序列化后,类增加或者减少字段,再反序列化会出现什么情况?

4. 固定SerialVersionUID不变,序列化后,类增加或者减少字段,再反序列化会出现什么情况?

序列化

简单来说,Java的序列化机制是通过判断类的SerialVersionUID来验证版本一致性的。在进行反序列化的时候,JVM会将传来的字节流中的SerialVersionUID与相对应的实体类中的SerialVersionUID进行比较,如果一致,就认为在上一次序列化到现在的反序列这个过程中,实体类没有发生改变,可以进行反序列化,如果不一致就会报错。

新建一个实体类Person.java

public class Person implements Serializable {

    private int id;
    private String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

新建序列化类SerialTest.java

public class SerialTest {
    public static void main(String[] args) {
        Person person = new Person(1, "Dimple");
        System.out.println("Person Serial: " + person.toString());
        try (
                FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);) {
            objectOutputStream.writeObject(person);
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

新建反序列化测试类DeSerialTest.java

public class DeSerialTest {
    public static void main(String[] args) {
        Person person = null;
        try (
                FileInputStream fileInputStream = new FileInputStream("Person.txt");
                ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);) {
            person = (Person) objectInputStream.readObject();
            System.out.println("DeSerial Person: " + person.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

至此,需要用到的类已经编写完毕,接下来就是一系列的实验:

实验

3. 保持SerialVersionUID不写,序列化后,类增加或者减少字段,再反序列化会出现什么情况?

4. 固定SerialVersionUID不变,序列化后,类增加或者减少字段,再反序列化会出现什么情况?

为了说明,我们将序列化的过程称为A过程,反序列化的过程称为B过程

实验一:保持SerialVersionUID不写,序列化后,类增加或者减少字段,再反序列化会出现什么情况?

实验步骤:

1. 运行SerialTest类main方法,在当前项目的根路径下生成person.txt,此为序列化。

2. 在Person类中增加age字段。

3. 运行DeSerialTest类main方法,检查是否异常。

结果:

java.io.InvalidClassException: com.dimple.serialversionUIDTest.Person; local class incompatible: stream classdesc serialVersionUID = -5720418216692542201, local class serialVersionUID = 6618704986964704271
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at com.dimple.springbootredis.serialversionUIDTest.DeSerialTest.main(DeSerialTest.java:20)

报错提示这两个类不匹配,并给出了stream中的SerialVersionUID为-5720418216692542201,本地的Person中的SerialVersionUID为6618704986964704271。

当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法名等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。

实验二:固定SerialVersionUID不变,序列化后,类增加字段,再反序列化会出现什么情况?

1. 在Person类中增加private static final long serialVersionUID = 123456789L;

2. 运行SerialTest类main方法,在当前项目的根路径下生成person.txt,此为序列化。

3. Person类中增加Intege age字段。

4. 运行DeSerialTest类main方法,检查是否异常。

结果:

无异常,age字段被A端忽略,赋值为null,如果age为基本类型int,那么会被赋值为0.

实验三:固定SerialVersionUID不变,序列化后,类减少字段,再反序列化会出现什么情况?

1. 在Person类中增加private static final long serialVersionUID = 123456789L;

2. 运行SerialTest类main方法,在当前项目的根路径下生成person.txt,此为序列化。

3. Person类中删除String name字段。

4. 运行DeSerialTest类main方法,检查是否异常。

结果:

无异常,name字段被B端忽略。

总结

1. 如果不写SerialVersionUID,那么JVM会自动生成SerialVersionUID,并且保证同一份文件只会生成同一个SerialVersionUID,如果对类作出修改会生成不同的SerialVersionUID。

2. SerialVersionUID的作用是区分版本,即不同版本的SerialVersionUID应该是不一样的。

3. 如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。