版本控制和 serialVersionUID

當你實現 java.io.Serializable 介面以使類可序列化時,編譯器會查詢 long 型別為 serialVersionUIDserialVersionUID 欄位。如果類沒有明確宣告該欄位,那麼編譯器將建立一個這樣的欄位併為其賦予一個值,該值來自 serialVersionUID 的依賴於實現的計算。此計算取決於類的各個方面,並遵循 Sun 提供的物件序列化規範 。但是,並不保證所有編譯器實現的值都相同。

此值用於檢查類與序列化的相容性,這是在對已儲存物件進行反序列化時完成的。Serialization Runtime 驗證 serialVersionUID 從反序列化資料中讀取,並且類中宣告的 serialVersionUID 完全相同。如果不是這樣的話,它就會引發一場騷亂。

強烈建議你明確宣告並初始化 long 型別的靜態最終欄位,並在你想要製作 Serializable 的所有類中命名為’serialVersionUID’,而不是依賴於該欄位值的預設計算,即使你不會使用版本控制。 ‘serialVersionUID’計算非常敏感,並且可能因編譯器實現而異,因此你可能會因為你在序列化過程的傳送方和接收方端使用不同的編譯器實現而在相同的類中獲得 InvalidClassException

public class Example implements Serializable {          
    static final long serialVersionUID = 1L /*or some other value*/;
    //...
}

只要 serialVersionUID 是相同的,Java Serialization 就可以處理不同版本的類。相容和不相容的變化是;

相容的變化

  • 新增欄位: 當正在重構的類具有未在流中出現的欄位時,物件中的該欄位將初始化為其型別的預設值。如果需要特定於類的初始化,則該類可以提供 readObject 方法,該方法可以將欄位初始化為非預設值。
  • 新增類: 流將包含流中每個物件的型別層次結構。將流中的此層次結構與當前類進行比較可以檢測其他類。由於流中沒有資訊來初始化物件,因此類的欄位將初始化為預設值。
  • 刪除類: 將流中的類層次結構與當前類的類層次結構進行比較可以檢測到類已被刪除。在這種情況下,從流中讀取與該類對應的欄位和物件。原始欄位將被丟棄,但已建立已刪除類引用的物件,因為它們稍後可能會在流中引用。當流被垃圾收集或重置時,它們將被垃圾收集。
  • 新增 writeObject / readObject 方法: 如果讀取流的版本具有這些方法,則通常需要 readObject 讀取預設序列化寫入流所需的資料。它應該在讀取任何可選資料之前先呼叫 defaultReadObject。期望 writeObject 方法像往常一樣呼叫 defaultWriteObject 來寫入所需的資料,然後可以編寫可選資料。
  • 新增 java.io.Serializable: 這相當於新增型別。此類的流中沒有值,因此其欄位將初始化為預設值。對非可序列化類的子類化的支援要求類的超型別具有無引數建構函式,並且類本身將被初始化為預設值。如果 no-arg 建構函式不可用,則丟擲 InvalidClassException。
  • 更改對欄位的訪問許可權 訪問修飾符 public,package,protected 和 private 不會影響序列化為欄位分配值的能力。
  • 將欄位從靜態更改為非靜態或瞬態更改為非瞬態: 當依賴預設序列化來計算可序列化欄位時,此更改等同於向類新增欄位。新欄位將寫入流,但較早的類將忽略該值,因為序列化不會將值分配給靜態或瞬態欄位。

不相容的變化

  • 刪除欄位: 如果在類中刪除了某個欄位,則寫入的流將不包含其值。當流被較早的類讀取時,該欄位的值將設定為預設值,因為流中沒有可用的值。但是,此預設值可能會對早期版本履行合同的能力產生不利影響。
  • 在層次結構中向上或向下移動類: 由於流中的資料以錯誤的順序出現,因此不允許這樣做。
  • 將非靜態欄位更改為靜態欄位或非瞬態欄位更改為瞬態: 依賴於預設序列化時,此更改等同於從類中刪除欄位。此類版本不會將該資料寫入流中,因此該類的早期版本無法讀取該資料。與刪除欄位時一樣,早期版本的欄位將初始化為預設值,這可能導致類以意外方式失敗。
  • 更改原始欄位的宣告型別: 類的每個版本都使用其宣告的型別寫入資料。嘗試讀取該欄位的類的早期版本將失敗,因為流中的資料型別與欄位的型別不匹配。
  • 更改 writeObject 或 readObject 方法,使其不再寫入或讀取預設欄位資料或更改它,以便它嘗試寫入或在先前版本沒有時讀取它。預設欄位資料必須始終顯示或不顯示在流中。
  • 將類從 Serializable 更改為 Externalizable 或反之亦然是一種不相容的更改,因為流將包含與可用類的實現不相容的資料。
  • 將類從非列舉型別更改為列舉型別,反之亦然,因為流將包含與可用類的實現不相容的資料。
  • 刪除 Serializable 或 Externalizable 是一種不相容的更改,因為在編寫時它將不再提供該類的舊版本所需的欄位。
  • 如果行為會產生與該類的任何舊版本不相容的物件,則將 writeReplace 或 readResolve 方法新增到類中是不相容的。