Java 序列化規範自動支援以下向後相容變更,ObjectInputStream 仍能正確讀取:
static 改為非 statictransient 改為非 transient若需移除欄位或將非 static/transient 改為 static/transient,則需額外處理。關鍵在於 serialVersionUID:序列化依據類別結構產生雜湊值,版本演進時必須保持相同 UID。可透過 serialver 工具取得原始類別的 UID,明確宣告於新版本中:
private static final long serialVersionUID = 1234567890L;
擁有相同 UID,即可雙向相容:舊資料反序列化為新類別(新欄位為預設值),新資料反序列化為舊類別(多餘欄位被忽略)。
enum Gender { MALE, FEMALE }
public class Person implements java.io.Serializable {
private static final long serialVersionUID = 1234567890L; // 固定 UID
private String firstName, lastName;
private int age;
private Person spouse;
private Gender gender; // 新增欄位
// 建構子、getter/setter、toString()...
}
Java 序列化二進位格式完全公開且可逆。直接將序列化流輸出到主控台,即可看到類別結構與私有欄位內容。透過 RMI 傳輸物件時,私有欄位幾乎以明文出現在 Socket 流中,存在資安風險。
實作 writeObject 與 readObject 方法,在序列化前/反序列化後轉換敏感欄位:
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
age <<= 2; // 序列化前:年齡左移 2 位(簡單模糊化)
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
in.defaultReadObject();
age >>= 2; // 反序列化後:還原年齡
}
注意:方法簽名必須完全符合規範(private、參數型別、拋出例外),否則 JVM 會靜默忽略,導致資料洩露。此技巧僅為示範,實務應使用強加密演算法。