Java serialVersionUID 有什么作用?

背景:

最近我們在做一個智能的對話機器人,是垂直領域的,然后會分析用戶的模型,在這個過程中會問用戶一些問題,之前用戶沒回答一次,我們就從DB中去把這個用戶的session給取出來,但是當以后用戶量很大的時候,這無疑給DB造成了很大的壓力,所以,我就加了一層緩存用的redis,因為要把對象序列化到redis,所以PO要實現那個Serializable接口,然后給一個UID,所以就想深入研究一下,這個serialVersionUID 有什么作用。

問題:

當一個對象實現 Serializable 接口時,多數 ide 會提示聲明一個靜態常量 serialVersionUID(版本標識),那 serialVersionUID 到底有什么作用呢?應該如何使用 serialVersionUID ?

探索歷程:

serialVersionUID 是實現 Serializable 接口而來的,而 Serializable 則是應用于Java 對象序列化/反序列化。對象的序列化主要有兩種用途:

  • 把對象序列化成字節碼,保存到指定介質上(如磁盤等)

  • 用于網絡傳輸

現在反過來說就是,serialVersionUID 會影響到上述所提到的兩種行為。那到底會造成什么影響呢?
java.io.Serializable doc 文檔,給出了一個相對詳細解釋:
 serialVersionUID 是 Java 為每個序列化類產生的版本標識,可用來保證在反序列時,發送方發送的和接受方接收的是可兼容的對象。如果接收方接收的類的 serialVersionUID 與發送方發送的 serialVersionUID 不一致,進行反序列時會拋出 InvalidClassException。序列化的類可顯式聲明 serialVersionUID 的值,如下:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;

當顯式定義 serialVersionUID 的值時,Java 根據類的多個方面(具體可參考 Java 序列化規范)動態生成一個默認的 serialVersionUID 。盡管這樣,還是建議你在每一個序列化的類中顯式指定 serialVersionUID 的值,因為不同的 jdk 編譯很可能會生成不同的 serialVersionUID 默認值,進而導致在反序列化時拋出 InvalidClassExceptions 異常。所以,為了保證在不同的 jdk 編譯實現中,其 serialVersionUID 的值也一致,可序列化的類必須顯式指定 serialVersionUID 的值。另外,serialVersionUID 的修飾符最好是 private,因為 serialVersionUID 不能被繼承,所以建議使用 private 修飾 serialVersionUID。

舉例說明如下: 現在嘗試通過將一個類 Person 序列化到磁盤和反序列化來說明 serialVersionUID 的作用: Person 類如下:

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;
    private Integer age;
    private String address;

    public Person() {
    }

    public Person(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }


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

簡單的測試一下:

@Test
public void testversion1L() throws Exception {
    File file = new File("person.out");
    // 序列化
    ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
    Person person = new Person("Haozi", 22, "上海");
    oout.writeObject(person);
    oout.close();
    // 反序列化
    ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
    Object newPerson = oin.readObject();
    oin.close();
    System.out.println(newPerson);
}

測試發現沒有什么問題。有一天,因發展需要, 需要在 Person 中增加了一個字段 email,如下:

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;
    private Integer age;
    private String address;
    private String email;

    public Person() {
    }

    public Person(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Person(String name, Integer age, String address,String email) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.email = email;
    }

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

這時我們假設和之前序列化到磁盤的 Person 類是兼容的,便不修改版本標識 serialVersionUID。再次測試如下:

@Test
public void testversion1LWithExtraEmail() throws Exception {
    File file = new File("person.out");
    ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
    Object newPerson = oin.readObject();
    oin.close();
    System.out.println(newPerson);
}

將以前序列化到磁盤的舊 Person 反序列化到新 Person 類時,沒有任何問題。

可當我們增加 email 字段后,不作向后兼容。即放棄原來序列化到磁盤的 Person 類,這時我們可以將版本標識提高,如下:

private static final long serialVersionUID = 2L;

再次進行反序列化,則會報錯,如下:

java.io.InvalidClassException:Person local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

談到這里,我們大概可以清楚,serialVersionUID 就是控制版本是否兼容的,若我們認為修改的 Person 是向后兼容的,則不修改 serialVersionUID;反之,則提高 serialVersionUID的值。再回到一開始的問題,為什么 ide 會提示聲明 serialVersionUID 的值呢?

因為若不顯式定義 serialVersionUID 的值,Java 會根據類細節自動生成 serialVersionUID 的值,如果對類的源代碼作了修改,再重新編譯,新生成的類文件的serialVersionUID的取值有可能也會發生變化。類的serialVersionUID的默認值完全依賴于Java編譯器的實現,對于同一個類,用不同的Java編譯器編譯,也有可能會導致不同的serialVersionUID。所以 ide 才會提示聲明 serialVersionUID 的值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,921評論 0 24
  • Java 語言支持的類型分為兩類:基本類型和引用類型。整型(byte 1, short 2, int 4, lon...
    xiaogmail閱讀 1,369評論 0 10
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,766評論 18 399
  • 窗外大雨淅瀝淅瀝,我早已不困,卻不愿起來,因為不知道起來后要做什么。繼續生老公的氣?找上三五好友約會?彈彈鋼...
    echoruru閱讀 232評論 0 0
  • 上個周日,朋友相邀出去放放風,欣然應允。但向來呆萌的我,直到坐上車了還不知目的地在何方,也不想關心,反正天清氣爽的...
    疏影橫斜閱讀 265評論 2 3