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

Java 每日一題:構造器賦值的 final 實例變量序列化反序列化行為驗證

     編輯:Java 綜合教學

題目代碼演進

V1.0 版(序列化端)

public class Ques1114 implements Serializable {
    private static final long serialVersionUID = 1L;
    public final int num;
    public Ques1114() { num = 123; }
}

V2.0 版(反序列化端,修改賦值邏輯)

public class Ques1114 implements Serializable {
    private static final long serialVersionUID = 1L;
    public final int num;
    public Ques1114() { num = 321; } // 唯一變化
}

問題

V1.0 對象序列化到磁碟 → 升級為 V2.0 類定義 → 反序列化讀取對象 → num 輸出多少?

答案與原理

輸出:123

關鍵規則

Java 序列化規範對 final 實例變量分兩類處理:

  1. 編譯時常量(static final 基本類型/String + 常量表達式賦值)→ 不序列化,反序列化時直接讀取類定義的常量值。
  2. 運行時賦值(構造器、實例初始化塊、方法調用賦值)→ 序列化寫入流,反序列化時直接還原流中的值,忽略當前類定義的賦值邏輯。

本題 num 在構造器賦值,屬於「運行時賦值」,因此:

  • 序列化時將 123 寫入對象流。
  • 反序列化時從流讀取 123 賦給 num,完全跳過 V2.0 構造器中的 num = 321。

對比上一題(20161115)

上題 num = init() 方法賦值,同屬「運行時賦值」,行為完全一致,輸出同為 123。

工程啟示

  1. final ≠ 不可變持久化:運行時賦值的 final 字段會被序列化,反序列化後仍保留舊值。
  2. 版本升級風險:若業務邏輯依賴 final 字段的「最新計算值」,升級後反序列化舊數據會導致邏輯不一致。
  3. 解決方案:
    • 去掉 final,改用 transient + 反序列化後重新計算(readObject())。
    • 或改為 static final 常量表達式(編譯時常量),但失去實例級差異化能力。
  4. serialVersionUID 必須維護:兩版本保持一致才能反序列化成功,否則拋 InvalidClassException。

驗證代碼片段

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.ser"));
oos.writeObject(new Ques1114()); // V1.0 實例,num=123
oos.close();

// 反序列化(類已換成 V2.0)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.ser"));
Ques1114 obj = (Ques1114) ois.readObject();
System.out.println(obj.num); // 輸出 123
ois.close();

理解此機制,能有效避免分布式系統、緩存中間件、RPC 框架中因對象序列化不一致引發的業務 Bug。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved