從根上讀懂阿里巴巴手冊 | 爲何不能輕易修改 serialVersionUID 字段

阿里巴巴開發手冊,(四)OOP 規約,第 13 條解釋以下:html

【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗;若是 徹底不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。 說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。java

首先須要解釋一下這條規則,並非要求你必定不能夠修改,而是根據本身的須要來修改。咱們先了解一下 serialVersionUID 是幹嗎的。git

序列化

首先咱們須要瞭解一下序列化,咱們能夠簡單了理解序列化就是把 Java 對象轉換成另外一種形態的數據,這種形態的數據能夠用於存儲或者是傳輸。由於自己 Java 對象是存在內存中,沒有辦法直接存儲或者是傳輸,序列化的出現來解決這個問題,那麼反序列化就是把形態數據從新轉換爲 Java 對象。固然這個形態能夠是多種格式,好比咱們詳知的 JSON,或者是 Byte,也能夠是咱們的自定義形式,好比 K-V 形式,以下圖。github

Java 默認序列化

說到 serialVersionUID 就不得不說到 Java 默認的序列化,爲了提供上文咱們說的存儲和傳出,Java 默認提供了一種序列化方式。只要序列化的類實現 java.io.Serializable 接口,這樣就能夠作序列化和反序列化了。我簡單羅列了一下代碼這樣更直觀(有所刪減)。web

User.javajson

public class User implements java.io.Serializable {
    private String name;
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}
複製代碼

SerializerTest.java微信

User user = new User("碼匠筆記");
FileOutputStream fo = new FileOutputStream("user.bytes");
ObjectOutputStream so = new ObjectOutputStream(fo);
so.writeObject(user);
FileInputStream fi = new FileInputStream("user.bytes");
ObjectInputStream si = new ObjectInputStream(fi);
user = (User) si.readObject();
複製代碼

代碼已經比較直觀,User 實現了 Serializable 接口,經過 ObjectOutputStream 把類轉化爲字節碼存入 user.bytes,而後再使用 ObjectInputStream 把字節碼從 user.bytes 讀入內存。 這樣很是簡單就實現了 Java 的序列化,說了這麼多它真的有用途嗎?固然,好比Java 自帶的遠程調用組件 RMIoracle

serialVersionUID

終於說到重點了,爲何不能輕易修改 serialVersionUID?但是上面的代碼中咱們明明就沒有設置 serialVersionUID。那咱們調整一下例子,再測試一次。 User.javajvm

public class User implements java.io.Serializable {
    private String name;
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}
複製代碼

UserSerializeTest.java編輯器

public class UserSerializeTest {
    public static void main(String[] args) throws Exception {
        User user = new User("碼匠筆記");
        FileOutputStream fo = new FileOutputStream("user.bytes");
        ObjectOutputStream so = new ObjectOutputStream(fo);
        so.writeObject(user);
        so.close();
    }
}
複製代碼

User.java

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;
    }
}
複製代碼

UserDeserializeTest.java

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();
    }
}
複製代碼

如上代碼,咱們先運行 UserSerializeTest.java,而後修改 User.java,添加一個屬性 desc,而後再次運行代碼。果真出錯了?

Exception in thread "main" java.io.InvalidClassException: 
com.github.codedrinker.p1413.User; 
local class incompatible: 
stream classdesc serialVersionUID = 6360520658036414457, 
local class serialVersionUID = -3025746955499933156
複製代碼

顯示 serialVersionUID 不相同,反序列化失敗了,但是咱們沒有定義 serialVersionUID 是爲何呢?(全部源碼文末均有獲取方式)
是時候讓源碼君出面了,咱們查看 java.io.ObjectStreamClass#writeNonProxy ,若是當前類(User)沒有定義 serialVersionUID,就會調用java.io.ObjectStreamClass#computeDefaultSUID生成默認的序列化惟一標示。咱們簡單的看代碼,發現他的生成規則是根據類名,結果明,方法和屬性等參數生成的 hash 值,因此咱們給 User 添加了 desc 屬性,因此對應的 serialVersionUID 確定會變化。

JVM 規範[1] 裏面也有具體的解釋

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

這裏咱們也可使用 JDK 自帶的工具 serialver 來驗證一下,這個工具和 JDK 默認的生成 serialVersionUID 的規則同樣。

serialver com.github.codedrinker.p1413.User
複製代碼

能夠獲得添加 desc 先後的 serialVersionUID ,輸出以下

// 未添加 desc
 private static final long serialVersionUID 
 = 6360520658036414457L;
 // 添加 desc
 private static final long serialVersionUID 
 = -3025746955499933156L;
複製代碼

好了,因此看到這裏咱們修改一個地方就能夠解決這個問題了。手工定義一個 serialVersionUID 代碼以下 User.java

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;
    }
}
複製代碼

這樣再重複上面的運行步驟,就能夠成功的作反序列化了。

到這裏咱們就所有明白了爲何文檔裏面說明不能輕易的修改 serialVersionUID 了。可是每次定義成 1L 也不是辦法,因此能夠配置一下 IDEA,這樣就能夠建立類的時候提示自動生成了。

其餘序列化方式

手冊裏面只提到了 Java 默認的序列化方式,其實還有不少性能很不錯的序列化方式,正如上文中提到的 JSON 或是字節流,好比如今比較流行的幾種:HessianKryoFastjsonProtoBufJackson 等,具體在這裏就不展開了,他們都有本身的優缺點,下面是 jvm-serializers[2] 輸出的它們的性能比較,能夠在選擇的時候有限參考下。

做者

更多精彩文章能夠掃碼關注做者微信訂閱號「碼匠筆記」,天天接收精彩推文。

源碼 & 手冊

連接: https://pan.baidu.com/s/1psiy00_0XeDk6RpsKFFKMA
提取碼: vftp
源碼: https://github.com/codedrinker/Head-First-Java-Alibaba-Coding-Guidelines

參考資料

[1]

JVM 規範: https://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100

[2]

jvm-serializers: https://github.com/eishay/jvm-serializers/wiki

相關文章
相關標籤/搜索