一、核心概念:什么是序列化與反序列化?
- 序列化 (Serialization): 將一個對象(在內存中的狀態)轉換成一個字節序列的過程。這個字節序列包含了對象的數據、對象的類型以及對象中存儲的屬性等信息。
- 反序列化 (Deserialization): 將序列化后得到的字節序列恢復為一個對象的過程。
目的
- 持久化存儲:將對象永久地保存到硬盤上的文件中,下次程序啟動時可以恢復。
- 網絡傳輸:將對象通過網絡從一個節點傳輸到另一個節點,例如在 RPC(遠程過程調用)、消息隊列或分布式系統中。
二、序列化的實現
????????Java 中要讓一個類的對象能夠被序列化,非常簡單:只需要讓這個類實現 java.io.Serializable 接口即可。Serializable 接口是一個標記接口(Marker Interface),它內部沒有任何方法需要實現。它的作用僅僅是“標記”這個類的對象是可序列化的,告訴 Java 虛擬機(JVM):“請注意,我這個類的對象可以被序列化哦!”
示例:定義一個可序列化的類
import java.io.Serializable;// 實現 Serializable 接口
public class Person implements Serializable {// 強烈建議顯式聲明 serialVersionUIDprivate static final long serialVersionUID = 1L; private String name;private int age;// transient 關鍵字標記的成員變量不會被序列化private transient String password; // 構造方法、getter、setter 等...public Person(String name, int age, String password) {this.name = name;this.age = age;this.password = password;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", password='" + password + '\'' + // 反序列化后 password 會是 null'}';}// ... 省略 getter 和 setter
}
關鍵點 1:serialVersionUID
- 是什么:一個類的序列化版本號。用于在反序列化時驗證序列化的對象和當前類的版本是否一致。
- 為什么重要:如果你不顯式聲明,JVM 會根據類的結構自動生成一個。一旦類的結構發生改變(比如增加了一個字段),自動生成的?
serialVersionUID
?也會改變,這將導致反序列化失敗,拋出?InvalidClassException
。 - 最佳實踐:強烈建議顯式聲明一個固定的?
serialVersionUID
(如?1L
)。這樣即使類后期增加了字段,只是反序列化時新字段為默認值,而不會直接失敗,保證了向后兼容性。
關鍵點 2:transient
?關鍵字
- 作用:用于修飾成員變量,表示該變量不參與序列化過程。
- 使用場景:用于保護敏感信息(如密碼、密鑰等),或者存儲一些沒必要序列化的臨時數據(如緩存、線程句柄等)。
三、序列化流與反序列化流的核心類
Java 提供了兩個專門的流來處理對象的序列化和反序列化:
ObjectOutputStream
: 序列化流,用于將對象寫入字節輸出流(如文件)。ObjectInputStream
: 反序列化流,用于從字節輸入流(如文件)中讀取并重建對象。
它們通常需要包裹在字節流(如?FileInputStream
/FileOutputStream
)之上,因為它們的底層操作仍然是字節。
序列化對象(寫入文件)
import java.io.*;public class SerializationDemo {public static void main(String[] args) {Person person = new Person("Alice", 30, "secret123");// try-with-resources 確保流正確關閉try (// 1. 創建節點流(字節流),指向目標文件FileOutputStream fos = new FileOutputStream("person.dat");// 2. 創建處理流(序列化流),包裹節點流ObjectOutputStream oos = new ObjectOutputStream(fos)) {// 3. 關鍵操作:將對象寫入(序列化)到文件oos.writeObject(person);System.out.println("對象序列化成功!");} catch (IOException e) {e.printStackTrace();}}
}
反序列化對象(從文件讀取)
import java.io.*;public class DeserializationDemo {public static void main(String[] args) {Person person = null;try (// 1. 創建節點流(字節流),連接到源文件FileInputStream fis = new FileInputStream("person.dat");// 2. 創建處理流(反序列化流),包裹節點流ObjectInputStream ois = new ObjectInputStream(fis)) {// 3. 關鍵操作:讀取(反序列化)字節流并重建對象// 需要強制類型轉換person = (Person) ois.readObject();System.out.println("對象反序列化成功!");System.out.println(person); // 調用 toString 方法} catch (IOException | ClassNotFoundException e) { // 注意 ClassNotFoundExceptione.printStackTrace();}// 輸出:Person{name='Alice', age=30, password='null'}// password 被 transient 修飾,所以反序列化后為 null(默認值)}
}
五、重要注意事項與特性
靜態變量不會被序列化
- 序列化是針對對象實例的,靜態變量屬于類,不屬于任何單個對象,所以不會被序列化。
引用類型的成員變量也必須可序列化
- 如果一個類有引用類型的成員變量(例如?
Person
?類里有一個?Address address
?字段),那么這個引用類型(Address
?類)也必須實現?Serializable
?接口,否則整個序列化過程會失敗,拋出?NotSerializableException
。
反序列化不會調用構造方法
- 對象是通過從流中讀取數據并直接賦值來重建的,構造方法不會被調用。