深入探索Java對象的序列化
對象序列化就是把對象寫入到輸出流中,用來存儲或者傳輸。
對象的反序列化就是從輸入流中讀取對象。
要序列化的對象應該實現Serializable接口。
Serializable接口是一個標識接口,沒有抽象方法。
Serializable有一個子接口Externalizable,實現Externalizable接口的類可以自行控制對象序列化荷反序列化過程。
一般來說,沒有必要自己實現序列化接口,直接交給Java虛擬機是上策。
實現了序列化接口的類,如果其成員不需要序列化進去,則使用transIEnt關鍵字進行修飾。
下面給出個例子:
import Java.io.*;
/**
* Java對象的序列化測試
* File: ObjectStreamTest.Java
* User: leizhimin
* Date: 2008-3-12 20:41:43
*/
public class ObjectStreamTest {
public static void main(String args[]) {
testObjectSeri();
testObjectInSeri();
}
/**
* 對象序列化測試
*/
public static void testObjectSeri() {
Person person = new Person("熔巖", "341022225562156", "lavasoft");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("Q:\\study\\Java5study\\src\\io\\person.dat");
oos = new ObjectOutputStream(fos);
oos.writeObject(person);
} catch (FileNotFoundException e) {
System.out.println("找不到指定的文件!");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 對象反序列化測試
*/
public static void testObjectInSeri() {
FileInputStream fis = null;
ObjectInputStream ois = null;
Person person = null;
try {
fis = new FileInputStream("Q:\\study\\Java5study\\src\\io\\person.dat");
ois = new ObjectInputStream(fis);
person = (Person) ois.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(person.toString());
}
}
/**
* 測試序列化所用的類
*/
class Person implements Serializable {
private String username;
private String cardNumber;
private transIEnt String passWord;
public Person(String username, String cardNumber, String passWord) {
this.username = username;
this.cardNumber = cardNumber;
this.password = passWord;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
public String getPassWord() {
return passWord;
}
public void setPassword(String passWord) {
this.password = passWord;
}
public String toString() {
StringBuffer sb = new StringBuffer(this.getClass().getName());
sb.append("[");
sb.append("\n\t");
sb.append("username=" + this.username);
sb.append("\n\t");
sb.append("cardNumber=" + this.cardNumber);
sb.append("\n\t");
sb.append("password=" + this.passWord);
sb.append("]");
return sb.toString();
}
}
運行結果為:
io.Person[
username=熔巖
cardNumber=341022225562156
passWord=null]
Process finished with exit code 0
屬性passWord=null,說明在序列化過程中忽略了。
說到此,還有一個容易忽略的問題--serialVersionUID :
序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化對象的發送者和接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應的發送者的類的版本號不同,則反序列化將會導致 InvalidClassException。可序列化類可以通過聲明名為 "serialVersionUID" 的字段(該字段必須是靜態 (static)、最終 (final) 的 long 型字段)顯式聲明其自己的 serialVersionUID:
ANY-Access-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基於該類的各個方面計算該類的默認 serialVersionUID 值,如“Java(TM) 對象序列化規范”中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因計算默認的 serialVersionUID 對類的詳細信息具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 Java 編譯器實現的一致性,序列化類必須聲明一個明確的 serialVersionUID 值。還強烈建議使用 private 修改器顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用於立即聲明類 -- serialVersionUID 字段作為繼承成員沒有用處。
serialVersionUID 在Eclipse裡可以自動生成,可是在其他大部分IDE工具裡面都不能自動生成。但是這個long型值取多少,心裡沒底,與其寫還不如不寫。