概述:當一個類實現了Serializable介面(該介面僅為標記介面,不包含任何方法定義),表示該類可以序列化.序列化的目的是將一個實現了Serializable介面的對象轉換成一個位元組序列,可以。
把該位元組序列儲存起來(例如:儲存在一個檔案裡),以後可以隨時將該位元組序列恢複為原來的對象。甚至可以將該位元組序列放到其他電腦上或者通過網路傳輸到其他電腦上恢複,只要該計
算機平台存在相應的類就可以正常恢複為原來的對象。
實現:要序列化一個對象,先要建立某些OutputStream對象,然後將其封裝在一個ObjectOutputStream對象內,再調用writeObject()方法即可序列化一個對象;還原序列化也類似。
import java.io.*;
public class Person implements Serializable {
private String userName;
private String password;
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一個對象(儲存到一個檔案)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Bruce", "123456"));
oos.close();
//還原序列化,將該對象恢複(儲存到一個檔案)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
//序列化一個對象(儲存到位元組數組)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos);
oos2.writeObject("Save another object:\n");
oos2.writeObject(new Person("Phil", "654321"));
oos2.close();
//還原序列化,將該對象恢複(儲存到位元組數組)
ObjectInputStream ois2 = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
s = (String)ois2.readObject();
p = (Person)ois2.readObject();
System.out.println(s + p);
}
}
//輸入如下資訊
Save a object:
userName:Bruce password:123456
Save another object:
userName:Phil password:654321
transient關鍵字:
自動序列化將對象的所有欄位都持久化了,有時候需要對某些欄位不進行自動化(如密碼,因為序列化會暴光密碼資訊),這個時候可以使用transient關鍵字(只能和Serializable對象一起使
用),其作用是不序列化某些欄位。將Person類的欄位改為如下定義,再運行上面的程式:
private String userName;
private transient String password;
//輸入如下資訊
Save a object:
userName:Bruce password:null
Save another object:
userName:Phil password:null
控制序列化欄位,甚至該欄位是被transient修飾的欄位也能將其序列化。手動序列化需要添加兩個私人(private)方法(writeObject()和readObject()),在該私人方法中控制序列花欄位。
import java.io.*;
public class Person implements Serializable {
private String userName;
private transient String password;
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); //序列化所有非transient欄位,必須是該方法的第一個操作
out.writeObject(password); //序列化transient欄位
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); //還原序列化所有非transient欄位,必須是該方法的第一個操作
password = (String)in.readObject(); //還原序列化transient欄位
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一個對象(儲存到一個檔案)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Bruce", "123456"));
oos.close();
//還原序列化,將該對象恢複(儲存到一個檔案)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
}
}
//輸入如下資訊
Save a object:
userName:David password:13579
控制序列化欄位還可以使用Externalizable介面替代Serializable借口。此時需要定義一個預設構造器,否則將為得到一個異常(java.io.InvalidClassException: Person; Person; no
valid constructor);還需要定義兩個方法(writeExternal()和readExternal())來控制要序列化的欄位。
import java.io.*;
public class Person implements Externalizable {
private String userName;
private String password;
public Person() {
System.out.println("default constructor invoked!");
}
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
public void writeExternal(ObjectOutput out) throws IOException {
//序列化欄位
out.writeObject(userName);
out.writeObject(password);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//還原序列化欄位
userName = (String)in.readObject();
password = (String)in.readObject();
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一個對象(儲存到一個檔案)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Leo", "1984"));
oos.close();
//還原序列化,將該對象恢複(儲存到一個檔案)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
}
}
//輸入如下資訊
default constructor invoked!
Save a object:
userName:Leo password:1984
關於Serializable介面的類中的serialVersionUID:
serialVersionUID是long類型的。在Eclipse中有兩種產生方式:
預設的是1L:
private static final long serialVersionUID = 1L;
另外一個則是根據類名、介面名、成員方法以及屬性等產生一個64位的雜湊欄位:
private static final long serialVersionUID = 3969438177161438988L;
serialVersionUID主要是為瞭解決對象還原序列化的相容性問題。
如果沒有提供serialVersionUID,對象序列化後存到硬碟上之後,再增加或減少類的filed。這樣,當還原序列化時,就會出現Exception,造成不相容問題。
但當serialVersionUID相同時,它就會將不一樣的field以type的預設值還原序列化。這樣就可以避開不相容問題了。
以上方式只能恢複成Java對象,如果想要恢複成其他對象(如C++對象),那就要將Java對象轉換為XML格式,這樣可以使其被各種平台和各種語言使用。可以使用隨JDK一起發布的javax.xam.*類庫,或者使用開源XOM類庫(可以從www.xom.nu下載並獲得文檔)。
使用java以來,序列化隨處可見,至於為什麼要用序列化、序列化能解決什麼問題,作為一個普通的碼農,一般不怎麼會去深入研究,由於最近在看mina和公司內部涉及到nio架構的一些源碼,裡面涉及到hession、java這兩種序列化,至於hession序列化為什麼會誕生以及在apache項目中使用如此廣泛,以及java本身序列化存在哪些缺陷,甚是不解,為瞭解答上面拋出來的疑惑,以及進一步瞭解java的序列化機制,這裡開個小頭,從java的序列化介面Serializable開始說起
jdk包裡的Serializable介面的注釋主要說明了以下幾點:
1.類通過實現Serializable介面來啟用序列化,否則該類的任何狀態將無法被序列化,同時也無法用於還原序列化
2.若繼承的父類沒有實現Serializable介面,但是又想讓子類可序列化,有三個注意事項:
a)。子類實現Serializable介面
b)。子類必須有可訪問的無參構造方法,用於儲存和恢複父類的public或protected或同包下的package欄位的狀態,否則在序列化或還原序列化時會拋出RuntimeException異常
c)。對於序列化後的子類,在進行還原序列化時,理論上無法初始化父類中private(不可訪問)物件變數的狀態或值
3.在對可序列化類別中的屬性進行序列化時,如果遇到不可序列化的物件變數,此時會針對不可序列化的類拋出NotSerializableException異常
4.對於可序列化的非數組類,強烈建議顯示聲明static型、long型、final型serialVersionUID欄位用於標識當前序列化類別的版本號碼,否則在跨作業系統、跨編譯器之間進行序列化和還原序列化時容易出現InvalidClassException異常
5.對於可序列化類別中的static、transient物件變數,在序列化時無法儲存其狀態或值,static物件變數在還原序列化時取得的值為當前jvm中對應類中對應static變數的值,而transient(瞬態)關鍵字則一般用於標識那些在序列化時不需要傳遞的狀態變數
簡單的測試代碼:
01 import java.io.FileInputStream;02 import java.io.FileNotFoundException;03 import java.io.FileOutputStream;04 import java.io.IOException;05 import java.io.ObjectInputStream;06 import java.io.ObjectOutputStream;07 import java.io.Serializable;08
09 /** 10 * 序列化測試11 * 12 * @author sume 13 * 14 */ 15 public
class SerializableImpl implements Serializable { 16
17 private static final long serialVersionUID = -6433786313435044319L;18
19 static String staticVal = "static1";20 transient String transientVal = "transient1";21 String val = "val1";22
23 /** 24 * main 25 */ 26 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { 27 // 序列化28 SerializableImpl sila1 = new SerializableImpl();29 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new
FileOutputStream("Serializable.txt"));30 objectOutputStream.writeObject(sila1);31 objectOutputStream.close();32
33 // 還原序列化34 SerializableImpl.staticVal = "static2";35 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("Serializable.txt"));36 SerializableImpl sila2 = (SerializableImpl) objectInputStream.readObject();37 objectInputStream.close();38
39 // 比較各個屬性的值40 System.out.println(sila2.staticVal);41 System.out.println(sila2.transientVal);42 System.out.println(sila2.val);43 } 44 }輸出結果:1 static2 2 null 3 val1從輸出結果可以看出:
1.還原序列化後類中static型變數staticVal的值為當前jvm中對應static變數的值,為:static2,而不是序列化時的值:static1
2.transient關鍵字標識的變數的狀態並沒有在序列化中被儲存,因此還原序列化後
transientVal變數的值為null
3.第三個為常見的對象狀態在序列化和還原序列化過程中的傳遞
簡單印證了前面所說的幾點內容