引入
阿里巴巴開發手冊中,第四章OOP規約的第13條解釋如下:
【強制】序列化類新增屬性時,請不要修改serialVersionUID字段,避免反序列失敗;如果 完全不兼容升級,避免反序列化混亂,那么請修改serialVersionUID值。說明:注意serialVersionUID不一致會拋出序列化運行時異常。
序列化
簡單來說,序列化就是把不適於存儲或傳輸的數據,轉化為另一種形式的數據,使得數據能夠得以保存或傳輸。相對的,反序列化就是將數據形式轉化這個過程逆向進行。
例子
比如說Java對象,就可以序列化為JSON,或者是Byte,也可以是我們的自定義形式,比如key-value形式,如下圖。
在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讀入內存。
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肯定會變化。
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;
}
}