Serializable 無方法,通知 JVM 該類可序列化Externalizable 完全接管讀寫邏輯// 序列化
Person person = new Person("熔巖", "341022225562156", "lavasoft");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.dat"))) {
oos.writeObject(person);
}
// 反序列化
Person person;
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.dat"))) {
person = (Person) ois.readObject();
}
System.out.println(person); // password 為 null
class Person implements Serializable {
private String username;
private String cardNumber;
private transient String passWord; // 不參與序列化
// getter/setter/toString
}
反序列化後 passWord 為 null(引用類型默認值),實現密碼等敏感信息不落盤。
反序列化時,JVM 比對流中 serialVersionUID 與當前類的 UID。不一致拋出 InvalidClassException。
private static final long serialVersionUID = 42L;(強烈推薦)private(繼承無意義)1L,兼容性修改不變更,破壞性修改(如刪除字段、改變類型)必須遞增private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先執行默認邏輯
out.writeObject(socket.getInetAddress()); // 手寫 transient 字段
out.writeInt(socket.getPort());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
InetAddress addr = (InetAddress) in.readObject();
int port = in.readInt();
this.socket = new Socket(addr, port); // 重建 transient 對象
}
關鍵:讀寫順序必須完全一致;調用 defaultWriteObject/ReadObject 保留自動處理非 transient 字段。
public class FullControl implements Externalizable {
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(field1);
out.writeInt(field2);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
field1 = in.readObject();
field2 = in.readInt();
}
}
風險:方法為 public,惡意代碼可調用竊取/篡改數據。敏感對象需加密流或安全管理器保護。
| 坑點 | 後果 | 對策 |
|---|---|---|
| 未聲明 serialVersionUID | 跨環境反序列化失敗 | 顯式聲明 private static final long |
| transient 字段反序列化後為 null/0 | 業務邏輯 NPE 或錯誤 | readObject 中重建或賦默認值 |
| 父類未實現 Serializable | 父類字段不序列化,反序列化調用無參構造器 | 父類也實現 Serializable 或子類手動處理 |
| 集合元素非 Serializable | 序列化拋出 NotSerializableException | 確保對象圖全鏈路可序列化 |
| 單例模式破壞 | 反序列化產生新實例 | 實現 readResolve() 返回單例實例 |
Java 序列化雖簡單強大,但 serialVersionUID、transient 重建、readResolve、安全性等細節決定生產可用性。原則:**顯式 UID、最小化序列化面、敏感數據加密、單例保護、充分測試跨版本兼容**。