阿里巴巴開發手冊,(四)OOP 規約,第 13 條解釋以下:html
【強制】序列化類新增屬性時,請不要修改
serialVersionUID
字段,避免反序列失敗;若是 徹底不兼容升級,避免反序列化混亂,那麼請修改serialVersionUID
值。 說明:注意serialVersionUID
不一致會拋出序列化運行時異常。java
首先須要解釋一下這條規則,並非要求你必定不能夠修改,而是根據本身的須要來修改。咱們先了解一下 serialVersionUID
是幹嗎的。git
首先咱們須要瞭解一下序列化,咱們能夠簡單了理解序列化就是把 Java
對象轉換成另外一種形態
的數據,這種形態
的數據能夠用於存儲或者是傳輸。由於自己 Java
對象是存在內存中,沒有辦法直接存儲或者是傳輸,序列化的出現來解決這個問題,那麼反序列化就是把形態
數據從新轉換爲 Java
對象。固然這個形態
能夠是多種格式,好比咱們詳知的 JSON
,或者是 Byte
,也能夠是咱們的自定義形式,好比 K-V
形式,以下圖。github
說到 serialVersionUID
就不得不說到 Java
默認的序列化,爲了提供上文咱們說的存儲和傳出,Java
默認提供了一種序列化方式。只要序列化的類實現 java.io.Serializable
接口,這樣就能夠作序列化和反序列化了。我簡單羅列了一下代碼這樣更直觀(有所刪減)。web
User.java
json
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
自帶的遠程調用組件 RMI
。oracle
終於說到重點了,爲何不能輕易修改 serialVersionUID
?但是上面的代碼中咱們明明就沒有設置 serialVersionUID
。那咱們調整一下例子,再測試一次。 User.java
jvm
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
或是字節流,好比如今比較流行的幾種:Hessian
、Kryo
、Fastjson
、ProtoBuf
、Jackson
等,具體在這裏就不展開了,他們都有本身的優缺點,下面是 jvm-serializers[2] 輸出的它們的性能比較,能夠在選擇的時候有限參考下。
更多精彩文章能夠掃碼關注做者微信訂閱號「碼匠筆記」,天天接收精彩推文。
連接: https://pan.baidu.com/s/1psiy00_0XeDk6RpsKFFKMA
提取碼: vftp
源碼: https://github.com/codedrinker/Head-First-Java-Alibaba-Coding-Guidelines
JVM 規範: https://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100
[2]jvm-serializers: https://github.com/eishay/jvm-serializers/wiki