1. 程式人生 > >序列化及Java Serializable序列化介面

序列化及Java Serializable序列化介面

2018.11.02

文章目錄

前言

某專案新版本上線,新版本中添加了A類,而A類最終會通過ObjectOutputStream#writeObject方法1序列化到HDFS中。而在上線完成之後才發現實現了Serializable序列化介面的A類並沒有定義serialVersionUID,換言之,如果下次上線的版本A類發生了修改,就很有可能會導致serialVersionUID改變,進而反序列化就會失敗。後果很重要,聞者都為虎軀一震。

Hello,序列化

序列化的定義就是將物件轉換成位元組流的過程1。那問題來了,物件好好的,為什麼要轉換成位元組流呢?

物件  ---------------------------------------------傳送方
   `
     ` (序列化)
       `
         `一串位元組流 --> 網路|磁碟...  --------------中間介質
                           `
                             `(反序列化)
                               `
                                 `
                                 物件  -------------接收方

首先物件它是存在於它所在機器的記憶體裡,一旦斷電或者程式執行結束,它就會消失。那一旦我們想要持久化這個物件或者通過網路傳輸2,該怎麼辦呢?要回答這個問題,我們需要知道,計算機的世界只有0/1,不管你是物件也好檔案也罷,最終的形態它都是一串位元組流。同一段位元組流,在JVM裡,它是個物件;但放到C++的執行時裡,它還會是個物件嗎,那就未必了。所以,如果當我們要在A環境中要將物件傳輸到B環境,無論我們是想要儲存還是僅僅是傳輸,傳輸過去的內容都只能是位元組流。

然後是怎麼序列化的問題。物件,我可以把它序列化成000011001,我也可以序列化成1000101101…01。那麼到底該怎麼序列化,才能讓接收它的人或者讀取它的人,能把它正確地還原回來(反序列化)。這就取決於序列化方法的實現。序列化方法其實就是金鑰,你用它來加密解密,那麼怎麼定義這個金鑰,視情況而定。在一般情況下,一個沒有自定義序列化方法的可序列化類,它的例項序列化出來的結果會把它所有的成員都包含進去,那麼反序列化出來的物件自然就是一個完整的例項。但如果考慮到效率、空間大小、實際需要等因素,這個類並不到所有成員都為接收它的人所需要,那麼就可以自定義序列化方法,將需要的內容進行序列化;而接收它的人,就只要按相應地規則將其反序列化出來即可。

Serializable:不能忽視的你

上節中,我們已經知道序列化方法決定了如何轉換。但是不是所有位元組流都值得或者能夠轉換呢?給你一個A類,你把它序列化並存儲到磁碟檔案;過段時間,A類變了,添加了一個新的成員,這時你還能不能用新的A類序列化方法將原本儲存在檔案裡的A例項反序列化出來呢?這個答案在理論上是可行的,但一個前提是你根據兩個版本A類的差異,自定義了A類的反序列化方法,另一個前提後面會講解。

一旦A類沒有自定義序列化/反序列化方法,那麼作為JVM,這種情況是不能允許檔案裡的物件反序列化成新A類的,因為它不符合新A類的定義。那執行時環境是如何保證這點的呢?答案就是第一節前言所提到的serialVersionUID

Java對每一個可序列化的類都會關聯上一個serialVersionUID序列版本id,用於標識這個類。類在沒有定義serialVersionUID的情況下,JVM會為它生成一個UID作為它的序列版本id,引用某書籍的表述3

Joshua Bloch says in Effective Java that the automatically generated UID is generated based on a class name, implemented interfaces, and all public and protected members. Changing any of these in any way will change the serialVersionUID.

也就是說,預設生成的serialVersionUID是會根據這個類的類名、公共成員等資訊得到一個特定的雜湊值。而在前言講的case裡,因為沒有定義serialVersionUID,所以一旦A類發生改動,那麼原本儲存到HDFS的資料,就將無法讀取,對於生產環境來說,這樣的問題是災難性的。

所以,Java Serializable介面在使用上非常重要的一點就是:請務必顯示地為可序列化類新增一個serialVersionUID序列版本id。語法如下:

private static final long serialVersionUID = -1L;

  1. Serialization by Microsoft ↩︎ ↩︎

  2. What’s this “serialization” thing all about? ↩︎

  3. How to generate serialVersionUID ↩︎