首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 序列化機制深度解析:Serializable、Externalizable、單例保護與版本演進

     編輯:Java 相關知識

Java 對象序列化核心機制與最佳實踐全景圖

序列化將對象狀態轉為字節流以便持久化或網絡傳輸,反序列化重建對象。看似簡單,實則涉及協議版本、單例破壞、敏感數據洩露、繼承關係等深層問題。

核心接口對比

特性SerializableExternalizable
類型標記接口 (無方法)功能接口 (兩方法)
默認機制JVM 自動遞歸序列化完全由開發者控制
構造器調用反序列化不調用構造器必須調用無參構造器
性能較慢 (反射、描述符)較快 (手工優化)
維護成本低高 (字段增減需改代碼)
適用場景通用業務對象、RMI 參數高性能、特殊格式、大對象

Serializable 進階控制

1. serialVersionUID:版本演進的鎖

// 顯式聲明,避免編譯器自動生成導致跨版本不兼容
private static final long serialVersionUID = 9038542591452547920L;
// 修改類結構 (增刪字段) 時:兼容舊數據則保持不變,不兼容則修改 UID

2. transient 關鍵字:敏感數據屏蔽

private transient String password; // 不參與序列化
private static int version = 1;    // static 也不序列化 (屬於類而非實例)

3. 自定義序列化:writeObject / readObject

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 保留默認行為。

4. PutField / GetField:精細控制字段級序列化

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));
}

Externalizable 完全手工控制

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 保證枚舉常量單例,反序列化直接返回常量引用。

繼承與序列化陷阱

  • 父類實現 Serializable:子類自動可序列化,父類字段按序列化
  • 父類未實現 Serializable:父類字段**不**序列化,反序列化時調用父類無參構造器初始化
  • 建議:父類也實現 Serializable,或提供無參構造器

現代替代方案建議

  1. JSON (Jackson/Gson):跨語言、可讀、版本友好、生態完善
  2. Protocol Buffers / Avro:高性能、強 Schema、RPC 首選
  3. Kryo / FST:Java 專用、比原生快 10 倍、支援循環引用
  4. 僅在 RMI、舊系統集成、HttpSession 複製等強制場景使用原生序列化
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved