transient 是 Java 序列化機制的「隱形開關」,標記欄位不參與序列化。但許多開發者只知其一、不知其二——反序列化時建構子不執行、預設值失效、靜態欄位天然排除等細節。本文透過實測代碼完整拆解。
public class LoggingInfo implements Serializable {
private Date loggingDate = new Date();
private String uid;
private transient String pwd; // 不會被序列化
public LoggingInfo(String user, String password) {
this.uid = user;
this.pwd = password;
}
}
序列化後反序列化,pwd 為 null (物件預設值),而非建構子設定的值。
JVM 反序列化時不呼叫任何建構子,而是直接分配記憶體並逐欄位恢復數值。因此:
transient 欄位無資料來源 → 保持型別預設值 (null/0/false)pwd = "guest"),反序列化後依然是 nullpublic class TransientDemo {
static class GuestInfo implements Serializable {
private String uid = "guest";
private transient String pwd = "guest"; // 建構子/預設值都給 guest
@Override public String toString() {
return "uid=" + uid + ", pwd=" + (pwd == null ? "NULL" : pwd);
}
}
public static void main(String[] args) throws Exception {
GuestInfo original = new GuestInfo();
System.out.println("原始物件: " + original); // uid=guest, pwd=guest
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("guest.ser"))) {
oos.writeObject(original);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("guest.ser"))) {
GuestInfo restored = (GuestInfo) ois.readObject();
System.out.println("反序列化: " + restored); // uid=guest, pwd=NULL
}
}
}
若需 transient 欄位反序列化後有特定值,實作 readObject:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先恢復非 transient 欄位
this.pwd = "DEFAULT_PWD"; // 手動給預設值
// 或從外部配置、金鑰庫載入真實密碼
}
| 誤區 | 事實 |
|---|---|
| static 欄位需加 transient 才不序列化 | static 欄位屬於類、不屬於實例,天然不參與序列化 |
| final + transient 會報錯 | 可共存,但 final transient 欄位反序列化後無法賦值 (編譯期常量除外) |
| transient 能防止敏感資料洩漏 | 僅防序列化洩漏,記憶體傾印、日誌、toString 仍可能暴露 |
fullName = firstName + lastName) → 加 transient,readObject 重算serialVersionUID 確保版本相容ObjectInputFilter 防反序列化攻擊transient 看似簡單,實則牽扯 JVM 物件佈局、序列化協議、安全審計等深層機制。掌握「繞過建構子、預設值失效、靜態天然排除」三大鐵律,才能在分散式系統、快取同步、安全合規場景下寫出零 Bug 的序列化代碼。