serialVersionUID 字段为何不能随便修改?

转载自文章面试官: 为什么不能轻易修改 serialVersionUID 字段?

引入

阿里巴巴开发手册中,第四章OOP规约的第13条解释如下:

【强制】序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果 完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。说明:注意serialVersionUID不一致会抛出序列化运行时异常。

序列化

简单来说,序列化就是把不适于存储或传输的数据,转化为另一种形式的数据,使得数据能够得以保存或传输。相对的,反序列化就是将数据形式转化这个过程逆向进行。

例子

比如说Java对象,就可以序列化为JSON,或者是Byte,也可以是我们的自定义形式,比如key-value形式,如下图。

Java序列化与反序列化

在Java中,默认提供了一种序列化方式。就是对应类实现java.io.Serializable 接口,就可以做到序列化和反序列化。下面分别是实现序列化的类User和测试类SerializerTest。

public class User implements java.io.Serializable {
    private String name;
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}
public class SerializerTest {
    public static void main(String[] args) throws Exception {
        User user = new User("LeoSyn");
        FileOutputStream fo = new FileOutputStream("user.bytes");
        ObjectOutputStream so = new ObjectOutputStream(fo);
        so.writeObject(user);
        so.close();
        FileInputStream fi = new FileInputStream("user.bytes");
        ObjectInputStream si = new ObjectInputStream(fi);
        user = (User) si.readObject();
        System.out.println(user);
        si.close();
    }
}

实现了Serializable接口的User类通过ObjectOutputStream转化为字节码存入user.bytes,再使用 ObjectInputStream把字节码从user.bytes读入内存。

Java序列化与反序列化例子

serialVersionUID

在Java中,类的serialVersionUID用于验证序列化和反序列化的类的版本是否一致。为何不能轻易修改serialVersionUID?调整一下例子,再测试一次。

例子

首先使用UserSerializeTest类对上一节例子中的User类进行序列化,存储到user.bytes中。

public class UserSerializeTest {
    public static void main(String[] args) throws Exception {
        User user = new User("LeoSyn");
        FileOutputStream fo = new FileOutputStream("user.bytes");
        ObjectOutputStream so = new ObjectOutputStream(fo);
        so.writeObject(user);
        so.close();
    }
}

随后对User类进行修改,增加一个新的变量desc。使用UserDeserializeTest类对user.bytes进行反序列化,生成User类对象。

public class UserDeserializeTest {
    public static void main(String[] args) throws Exception {
        FileInputStream fi = new FileInputStream("user.bytes");
        ObjectInputStream si = new ObjectInputStream(fi);
        User user = (User) si.readObject();
        System.out.println(user);
        si.close();
    }
}
public class User implements java.io.Serializable {
    private String name;
    private String desc;
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

执行结果如下。报错显示serialVersionUID不同,反序列化失败。但是代码中并没有定义serialVersionUID,原理是什么呢?

Exception in thread "main" java.io.InvalidClassException: com.serializationTest.User; 
local class incompatible: stream classdesc serialVersionUID = 6360520658036414457, 
local class serialVersionUID = 5259168347869896042
        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.serializationTest.UserDeserializeTest.main(UserDeserializeTest.java:10)

源码解析

在java.io.ObjectStreamClass#writeNonProxy中,如果当前类没有定义serialVersionUID,就会调用java.io.ObjectStreamClass#computeDefaultSUID生成默认的序列化唯一标识。代码中生成唯一标识的规则是根据类名,接口名,方法和属性等参数生成的hash值,所以给User添加了desc属性,对应的serialVersionUID肯定会变化。

Java序列化与反序列化例子_报错

JVM规范里也有具体的解释:

The stream-unique identifier is a 64-bit hash of the class name, interface class names, methods, and fields.

修改方案

在上一节例子场景中,只要给User类定义一个serialVersionUID,即使在序列化后对User类进行修改,再进行反序列化,也可以成功执行代码。代码如下:

public class User implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

Java序列化与反序列化例子_修改

猜你喜欢

转载自www.cnblogs.com/leosyn1998/p/12563348.html