筆記:I/O流-對象序列化
Java 語言支持一種稱為對象序列化(Object Serialization)的非常通用的機制,可以將任何對象寫入到流中,並在之後將其讀回,首先需要支持對象序列化的類,必須繼承與 Serializable 接口,該接口沒有任何方法,只是對類起到標記的作用,然後使用 ObjectOutputStream 流來序列化對象,使用 ObjectInputStream 流來反序列化,示例代碼如下:
- 對象類聲明:
public class Employee implements Serializable {
????????private String name;
????????private String sex;
????????public Employee() {
????????}
????????public Employee(String name, String sex) {
????????????????this.name = name;
????????????????this.sex = sex;
????????}
????????// getter 和 setter 方法
}
? ?
public class Manager extends Employee {
????????private Employee secretary;
????????public Manager(){
????????}
????????public Manager(String name, String sex) {
????????????????super(name, sex);
????????}
????????// getter 和 setter 方法
}
- 創建對象實例:
?Employee harry = new Employee("Harry Hacker", "男");
?Manager boss = new Manager("Carl Cracker", "女");
boss.setSecretary(harry);
- 序列化到文件
? ObjectOutputStream outputStream = null;
?????????try {
??????????????? outputStream = new ObjectOutputStream(new FileOutputStream("serializableApp.dat"));
????????????????outputStream.writeObject(boss);
???????? } catch (FileNotFoundException ex) {
????????????????ex.printStackTrace();
???????? ?} finally {
????????????????if (outputStream != null) {
???????????????????????outputStream.close();
????????????????}
??????? ?}
- 從文件反序列化
????????????????ObjectInputStream inputStream = null;
????????????????try {
????????????????????????inputStream = new ObjectInputStream(new FileInputStream("serializableApp.dat"));
????????????????????????Manager serializableBoss = (Manager) inputStream.readObject();
????????????????????????System.out.println("manager name is " + serializableBoss.getName() + " sex is "
????????????????????????????????????????+ serializableBoss.getSex() + " secretary is "
????????????????????????????????????????+ serializableBoss.getSecretary().getName());
????????????????} catch (ClassNotFoundException ex) {
????????????????????????ex.printStackTrace();
????????????????} catch (FileNotFoundException ex) {
????????????????????????ex.printStackTrace();
????????????????} finally {
????????????????????????if (inputStream != null) {
????????????????????????????????inputStream.close();
????????????????????????}
????????????????}
每個對象都用一個序列號保存的,對象序列化機制如下:
- 對於遇到的每一個對象引用都關聯一個序列號
- 對於每一個對象,當第一次遇到時,保存器對象數據到流中
- 如果某個對象已經被保存過,那麽只寫出保存的序列號
- 對於流中的對象,在第一次遇到其序列號時,創建他,並使用流中數據來初始化他,然後記錄這個順序號和新對象之間的關聯
- 當遇到對象引用另一個對象的序列號時,獲取與這個序列號相關聯的對象引用
某些數據域時不可以序列化的,例如,只對本地方法有意義的存儲文件句柄或窗口句柄的整數值等,Java 擁有一種簡單的機制來防止這種域被序列化,那就是將他們標記成 transient,如果被標記為不可序列化的類,也需要將其標記為 transient,瞬時域在對象序列化時總是被跳過,示例如下:
????private transient Point2D.Double point;
? ?
- 修改默認的序列化機制
序列化機制單個的類提供了一種方式,去向默認的讀寫行為添加驗證或任何其他想要的行為,可序列化類可以定義具體有如下簽名的方法:
????????private void readObject(ObjectInputStream in)
????????????????????????throws IOException,ClassNotFoundException;
???????? ?
????????private void writeObject(ObjectOutputStream out)
????????????????????????throws IOException;
readObject 和 writeObject 方法只需要保存和加載本類的數據域,而不需要關註基類(超類)數據和任何其他類的信息,實現給方法的具體示例如下:
???? private void readObject(ObjectInputStream in)
????????????????????????throws IOException, ClassNotFoundException {
????????????????// 讀取序列化字段數據
????????????????in.defaultReadObject();
????????????????// 其他數據校驗或者序列化
????????}
????????private void writeObject(ObjectOutputStream out)
????????????????????????throws IOException{
????????????????// 寫入序列化字段數據
????????????????out.defaultWriteObject();
????????????????// 其他數據校驗或者序列化
???????}
除了可以使用readObject 和 writeObject方法來保存和恢復對象數據外,類還可以定義他自己的機制,類需要實現 Externalizable 接口,該接口定義了兩個方法:
????public void readExternal(ObjectInput in)
???????? throws IOException, ClassNotFoundException;
? ?
????public void writeExternal(ObjectOutput out)
????????????throws IOException ;
這些方法對包括超類數據在內的整個對象的存儲和恢復負全責,而序列化機制在流中僅僅只是記錄該對象所屬的類,示例代碼如下:
- 基類代碼:
????????public void writeExternal(ObjectOutput out) throws IOException {
????????????????out.writeUTF(this.name);
????????????????out.writeUTF(this.sex);
????????}
????????public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
????????????????this.name = in.readUTF();
????????????????this.sex = in.readUTF();
????????}
- 子類代碼:
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
????????super.readExternal(in);
????????this.secretary = new Employee();
??????? this.secretary.readExternal(in);
?}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
????????super.writeExternal(out);
???????this.secretary.writeExternal(out);
?}
? ?
- 序列化單例和類型安全的枚舉
在序列化和反序列化時,如果目標對象時唯一的,使用默認的序列化機制時不適用的,因為默認的序列化機制,即使構造器時私有的,序列化機制也可以創建新的對象,因此在進行==(比較)時將失敗,為了解決整個問題需要定義一個名稱為 readResolve的特殊方法,在對象被序列化之後就會調用他,返回一個對象,而該對象之後會稱為 readObject 的返回值,示例代碼如下:
public class Orientation implements Serializable {
????????public static final Orientation HORIZONTAL = new Orientation(1);
????????public static final Orientation VERTICAL = new Orientation(2);
? ?
????????private int value;
? ?
????????private Orientation(int value) {
????????????????this.value = value;
????????}
? ?
????????protected Object readResolve() throws ObjectStreamException {
????????????????if (value == 1) {
????????????????????????return HORIZONTAL;
????????????????}
????????????????if (value == 2) {
????????????????????????return VERTICAL;
????????????????}
???????????????? ?
????????????????return null;
????????}
}
- 版本管理
無論類的定義產生了什麽樣的變化,他的SHA指紋也會跟著變化,而我們知道對象流拒絕讀入具有不同指紋的對象,但是,類可以表明他對其早期版本保持兼容,在類的所有較新的版本都必須把 serialVersionUID 常量定義與最初版本的指紋相同,如果一個類具有名為 serialVersionUID 的靜態數據成員,就不需要在人工的計算其指紋,而只需直接使用整個值,示例如下:
public class Employee implements Serializable, Externalizable {
????????public static final long serialVersionUID = -2349238498234324L;
}
如果類只有方法產生了變化,那麽在讀入新對象數據時是不會有任何問題的,如果是數據域產生了變化,那麽就可能會有問題,常見情況如下:
- 如果數據域之間名字匹配而類型不匹配,那麽對象流不會進行類型轉換,因此不兼容
- 如果流中的對象具有當前版本中所沒有的數據域,那麽對象流會忽視這些額外的數據
- 如果當前版本具有在流化對象中所沒有的數據域,那麽這些新增加的域將被設置成他們的默認值(對象是null、數字為 0,布爾類型為 false)
? ?
筆記:I/O流-對象序列化