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序列化與反序列化例子_修改


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM