序列化將對象狀態轉為字節流以便持久化或網絡傳輸,反序列化重建對象。看似簡單,實則涉及協議版本、單例破壞、敏感數據洩露、繼承關係等深層問題。
| 特性 | Serializable | Externalizable |
|---|---|---|
| 類型 | 標記接口 (無方法) | 功能接口 (兩方法) |
| 默認機制 | JVM 自動遞歸序列化 | 完全由開發者控制 |
| 構造器調用 | 反序列化不調用構造器 | 必須調用無參構造器 |
| 性能 | 較慢 (反射、描述符) | 較快 (手工優化) |
| 維護成本 | 低 | 高 (字段增減需改代碼) |
| 適用場景 | 通用業務對象、RMI 參數 | 高性能、特殊格式、大對象 |
// 顯式聲明,避免編譯器自動生成導致跨版本不兼容
private static final long serialVersionUID = 9038542591452547920L;
// 修改類結構 (增刪字段) 時:兼容舊數據則保持不變,不兼容則修改 UID
private transient String password; // 不參與序列化
private static int version = 1; // static 也不序列化 (屬於類而非實例)
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先執行默認邏輯
out.writeObject(encrypt(password)); // 再寫入加密後的密碼
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.password = decrypt((String) in.readObject()); // 解密還原
}
原理:JVM 通過反射檢測並調用這兩個私有方法,defaultWriteObject/ReadObject 保留默認行為。
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectOutputStream.PutField fields = out.putFields();
fields.put("name", name);
fields.put("password", "ENC:" + encrypt(password)); // 重命名/加密
out.writeFields();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = in.readFields();
name = (String) fields.get("name", "");
String enc = (String) fields.get("password", "ENC:");
password = decrypt(enc.substring(4));
}
public class User implements Externalizable {
private String name;
private transient int age; // transient 在此無效,完全由代碼決定
public User() {} // 必須有 public 無參構造器
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
// gender 故意不寫入 -> 反序列化為 null
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
}
問題:反序列化每次創建新對象,破壞單例。
// 解決:readResolve 替換反序列化產生的對象
private Object readResolve() throws ObjectStreamException {
return INSTANCE; // 返回枚舉實例或靜態持有的單例
}
枚舉單例天然免疫:JVM 保證枚舉常量單例,反序列化直接返回常量引用。