網上不少資料將 transient 與 volatile 混淆,甚至錯誤標註。本文釐清兩者核心差異,並深入解析 transient 在序列化中的完整行為。
| 維度 | transient | volatile |
|---|---|---|
| 核心領域 | 對象序列化/持久化 | 多線程內存可見性 |
| 作用目標 | 實例字段 | 實例/靜態字段 |
| 效果 | 字段不參與序列化,反序列化為默認值 | 強制從主內存讀寫,禁止指令重排序 |
| 典型場景 | 敏感數據、派生字段、非序列化對象引用 | 狀態標誌、雙重檢查鎖、單例模式 |
volatile 保證:
i++ 等複合操作仍需同步或原子類// 典型用法:線程停止標誌
volatile boolean running = true;
void stop() { running = false; }
void run() { while (running) { /* ... */ } }
static/final 字段雖可加但無實際效果(static 不屬於實例,final 序列化由編譯器處理)null,基本類型 → 0/falseclass GuestLoggingInfo implements Serializable {
private String uid = "guest";
private transient String pwd = "guest"; // 有默認值
public GuestLoggingInfo() {} // 無參構造函數
public String toString() {
String password = (pwd == null) ? "NOT SET" : pwd;
return "user: " + uid + "\npassword: " + password;
}
}
// 測試
GuestLoggingInfo g = new GuestLoggingInfo();
System.out.println("序列化前: " + g); // password: guest
// 序列化 + 反序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("guest.ser"));
oos.writeObject(g);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("guest.ser"));
GuestLoggingInfo g2 = (GuestLoggingInfo) ois.readObject();
System.out.println("反序列化後: " + g2); // password: NOT SET ← 構造函數未執行!
關鍵點:反序列化不調用構造函數,直接分配內存並填充非 transient 字段,transient 字段保持默認值(null),而非構造函數賦予的默認值。
class SecureData implements Serializable {
private transient String decryptedCache; // 解密後緩存,不序列化
private String encryptedData; // 加密存儲
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 默認序列化非 transient 字段
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 默認反序列化
// 此處可觸發解密邏輯重建 decryptedCache
this.decryptedCache = decrypt(encryptedData);
}
}
volatile 解決並發可見性,是多線程編程利器transient 解決序列化選擇性,是持久化/傳輸安全衛士transient 「不調用構造函數」特性,避免反序列化後字段狀態不符預期