建議11: 養成良好習慣,顯式聲明UID
我們編寫一個實作了Serializable接口(序列化标志接口)的類, Eclipse馬上就會給一個黃色警告:需要增加一個Serial Version ID。為什麼要增加?它是怎麼計算出來的?有什麼用?本章就來解釋該問題。
類實作Serializable接口的目的是為了可持久化,比如網絡傳輸或本地存儲,為系統的分布和異構部署提供先決支援條件。若沒有序列化,現在我們熟悉的遠端調用、對象資料庫都不可能存在,我們來看一個簡單的序列化類:
這是一個簡單JavaBean,實作了Serializable接口,可以在網絡上傳輸,也可以本地存儲然後讀取。這裡我們以Java消息服務(Java Message Service)方式傳遞該對象(即通過網絡傳遞一個對象),定義在消息隊列中的資料類型為ObjectMessage,首先定義一個消息的生産者(Producer),代碼如下:
這裡引入了一個工具類SerializationUtils,其作用是對一個類進行序列化和反序列化,并存儲到硬碟上(模拟網絡傳輸),其代碼如下:
通過對象序列化過程,把一個對象從記憶體塊轉化為可傳輸的資料流,然後通過網絡發送到消息消費者(Consumer)那裡,并進行反序列化,生成執行個體對象,代碼如下:.
這是一個反序列化過程,也就是對象資料流轉換為一個執行個體對象的過程,其運作後的輸出結果為:混世魔王。這太easy了,是的,這就是序列化和反序列化典型的demo。但此處隐藏着一個問題:如果消息的生産者和消息的消費者所參考的類(Person類)有差異,會出現何種神奇事件?比如:消息生産者中的Person類增加了一個年齡屬性,而消費者沒有增加該屬性。為啥沒有增加?!因為這是個分布式部署的應用,你甚至都不知道這個應用部署在何處,特别是通過廣播(broadcast)方式發送消息的情況,漏掉一兩個訂閱者也是很正常的。
這個時候給Person.java類增加一個age屬性.
直接運作Consumer.java 會抛出 java.io.InvalidClassException異常
在這種序列化和反序列化的類不一緻的情形下,反序列化時會報一個InvalidClassException異常,原因是序列化和反序列化所對應的類版本發生了變化,JVM不能把資料流轉換為執行個體對象。接着刨根問底:JVM是根據什麼來判斷一個類版本的呢?
好問題,通過SerialVersionUID,也叫做流辨別符(Stream Unique Identifier),即類的版本定義的,它可以顯式聲明也可以隐式聲明。顯式聲明格式如下:
而隐式聲明則是我不聲明,你編譯器在編譯的時候幫我生成。生成的依據是通過包名、類名、繼承關系、非私有的方法和屬性,以及參數、傳回值等諸多因子計算得出的,極度複雜,基本上計算出來的這個值是唯一的。
serialVersionUID如何生成已經說明了,我們再來看看serialVersionUID的作用。JVM在反序列化時,會比較資料流中的serialVersionUID與類的serialVersionUID是否相同,如果相同,則認為類沒有發生改變,可以把資料流load為執行個體對象;如果不相同,對不起,我JVM不幹了,抛個異常InvalidClassException給你瞧瞧。這是一個非常好的校驗機制,可以保證一個對象即使在網絡或磁盤中“滾過”一次,仍能做到“出淤泥而不染”,完美地實作類的一緻性。
但是,有時候我們需要一點特例場景,例如:我的類改變不大,JVM是否可以把我以前的對象反序列化過來?就是依靠顯式聲明serialVersionUID,向JVM撒謊說“我的類版本沒有變更”,如此,我們編寫的類就實作了向上相容。我們修改一下上面的Person類,代碼如下:
Person.java類上加上了serialVersionUID之後再進行序列化的Producer.java和反序列化的Consumer.java之後
是不會抛出InvalidClassException異常的.
剛開始生産者和消費者持有的Person類版本一緻,都是V1.0,某天生産者的Person類版本變更了,增加了一個“年齡”屬性,更新為V2.0,而由于種種原因(比如程式員疏忽、更新時間視窗不同等)消費端的Person還保持為V1.0版本,代碼如下:
此時雖然生産者和消費者對應的類版本不同,但是顯式聲明的serialVersionUID相同,反序列化也是可以運作的,所帶來的業務問題就是消費端不能讀取到新增的業務屬性(age屬性)而已。
通過此例,我們的反序列化實作了版本向上相容的功能,使用V1.0版本的應用通路了一個V2.0版本的對象,這無疑提高了代碼的健壯性。我們在編寫序列化類代碼時,随手加上serialVersionUID字段,也不會給我們帶來太多的工作量,但它卻可以在關鍵時候發揮異乎尋常的作用。
注意 顯式聲明serialVersionUID可以避免對象不一緻,但盡量不要以這種方式向JVM“撒謊”。
<col>
作者:SummerChill