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