天天看點

《Java核心技術 卷Ⅱ 進階特性(原書第10版)》一2.4.5 版本管理

如果使用序列化來儲存對象,就需要考慮在程式演化時會有什麼問題。例如,1.1版本可以讀入舊檔案嗎?仍舊使用1.0版本的使用者可以讀入新版本産生的檔案嗎?顯然,如果對象檔案可以處理類的演化問題,那它正是我們想要的。

乍一看,這好像是不可能的。無論類的定義産生了什麼樣的變化,它的SHA指紋也會跟着變化,而我們都知道對象輸入流将拒絕讀入具有不同指紋的對象。但是,類可以表明它對其早期版本保持相容,要想這樣做,就必須首先獲得這個類的早期版本的指紋。我們可以使用JDK中的單機程式serialver來獲得這個數字,例如,運作下面的指令

《Java核心技術 卷Ⅱ 進階特性(原書第10版)》一2.4.5 版本管理

如果在運作serialver程式時添加-show選項,那麼這個程式就會産生下面的圖形化對話框(參見圖2-7)。

《Java核心技術 卷Ⅱ 進階特性(原書第10版)》一2.4.5 版本管理

這個類的所有較新的版本都必須把serialVersionUID常量定義為與最初版本的指紋相同。

《Java核心技術 卷Ⅱ 進階特性(原書第10版)》一2.4.5 版本管理

如果一個類具有名為serialVersionUID的靜态資料成員,它就不再需要人工地計算其指紋,而隻需直接使用這個值。

一旦這個靜态資料成員被置于某個類的内部,那麼序列化系統就可以讀入這個類的對象的不同版本。

如果這個類隻有方法産生了變化,那麼在讀入新對象資料時是不會有任何問題的。但是,如果資料域産生了變化,那麼就可能會有問題。例如,舊檔案對象可能比程式中的對象具有更多或更少的資料域,或者資料域的類型可能有所不同。在這些情況中,對象輸入流将盡力将流對象轉換成這個類目前的版本。

對象輸入流會将這個類目前版本的資料域與被序列化的版本中的資料域進行比較,當然,對象流隻會考慮非瞬時和非靜态的資料域。如果這兩部分資料域之間名字比對而類型不比對,那麼對象輸入流不會嘗試将一種類型轉換成另一種類型,因為這兩個對象不相容;如果被序列化的對象具有在目前版本中所沒有的資料域,那麼對象輸入流會忽略這些額外的資料;如果目前版本具有在被序列化的對象中所沒有的資料域,那麼這些新添加的域将被設定成它們的預設值(如果是對象則是null,如果是數字則為0,如果是boolean值則是false)。

下面是一個示例:假設我們已經用雇員類的最初版本(1.0)在磁盤上儲存了大量的雇員記錄,現在我們在Employee類中添加了稱為department的資料域,進而将其演化到了2.0版本。圖2-8展示了将1.0版的對象讀入到使用2.0版對象的程式中的情形,可以看到department域被設定成了null。圖2-9展示了相反的情況:一個使用1.0版對象的程式讀入了2.0版的對象,可以看到額外的department域被忽略。

《Java核心技術 卷Ⅱ 進階特性(原書第10版)》一2.4.5 版本管理

這種處理是安全的嗎?視情況而定。丢掉資料域看起來是無害的,因為接收者仍舊擁有它知道如何處理的所有資料,但是将資料域設定為null卻有可能并不那麼安全。許多類都費盡心思地在其所有的構造器中将所有的資料域都初始化為非null的值,以使得其各個方法都不必去處理null資料。是以,這個問題取決于類的設計者是否能夠在readObject方法中實作額外的代碼去訂正版本不相容問題,或者是否能夠確定所有的方法在處理null資料時都足夠健壯。