serialVersionUID

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("John", 21, "廣州");
    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 的值。

附錄拓展:

stackoverflow原址:http://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章