Serializable介面與serialVersionUID
一個類實現Serializable介面後可以被序列化。這個介面沒有方法和欄位,只是用來標誌這個類可以被序列化。
如果父類實現了serializable介面,那麼子類實現還是不實現介面都一樣。子類和父類所有的非static,非transient的欄位的值都能被儲存和恢復。
如果父類沒有實現serializable介面,那麼父類必須有無參的且可被子類訪問的建構函式,但是不會儲存父類的所有欄位的值。
在反序列化的時候,沒有實現serializable介面的類將會呼叫無參的建構函式進行例項化。而無參的建構函式必須可以被子類(進行反序列化的類)訪問,子類的欄位都會被恢復。
如果父類不實現 Serializable介面的話,就需要有預設的無參的建構函式。這是因為一個 Java 物件的構造必須先有父物件,才有子物件,反序列化也不例外。在反序列化時,為了構造父物件,只能呼叫父類的無參建構函式作為預設的父物件。因此當我們取父物件的變數值時,它的值是呼叫父類無參建構函式後的值。在這種情況下,在序列化時根據需要在父類無參建構函式中對變數進行初始化,否則的話,父類變數值都是預設宣告的值,如 int 型的預設是 0,string 型的預設是 null。
如果需要在序列化和反序列化時做特殊處理,那麼類中需要有這幾個方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
writeObject可以記錄一個類的狀態資訊用於readObject恢復。在這個方法中可以呼叫out.defaultWriteObject來記錄預設的類資訊。
如果被序列化的類中有Object writeReplace()方法,那麼在序列化的時候,會被呼叫。用來替換當前類
讀取一個類的class資訊,類簽名,非transient的,非靜態的欄位值。
objectInputStream.readObject();
父類:
public class ParentPer { private String p1; private static String p2; protected int p3; public int p4; ParentPer(){ System.out.println("父類構造方法執行了"); } @Override public String toString() { return p1+"_"+p2+"_"+p3+"_"+p4; } public String getP1() { return p1; } public void setP1(String p1) { this.p1 = p1; } public static String getP2() { return p2; } public static void setP2(String p2) { ParentPer.p2 = p2; } }
子類:
public class ChildPtr extends ParentPer implements Serializable{
private String c1;
private static String c2;
protected int c3;
public int c4;
public ChildPtr(String c1, String c2) {
super();
this.c1 = c1;
ChildPtr.c2 = c2;
super.setP1("p1");
setP2("p2");
p3=5;
p4=6;
}
@Override
public String toString() {
return c1+"_"+c2+"_"+c3+"_"+c4+" "+super.toString();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ChildPtr childPtr = new ChildPtr("c1", "c2");
childPtr.c3=9;
childPtr.c4=10;
byte[] c = SerializableUtil.serialiable(childPtr);
ByteArrayInputStream bi = new ByteArrayInputStream(c);
ObjectInputStream objectInputStream = new ObjectInputStream(bi);
ChildPtr cc = (ChildPtr) objectInputStream.readObject();
System.out.println(childPtr);
System.out.println(cc);
}
}
serialVersionUID欄位表示類的序列化版本,用於反序列化時校驗。如果反序列化時的類的serialVersionUID與序列化時不同,那麼會丟擲InvalidClassException異常。
必須是final和static修飾的,推薦使用private修飾,因為它不需要被繼承使用,只在序列化和反序列化時使用。
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果類中沒有這個欄位,那麼在執行時jvm會幫忙計算一個值。推薦使用者給每一個有序列化能力的類明確指定一個serialVersionUID 。因為預設的計算方式是嚴重依賴於編譯器的實現,可能導致反序列化的時候丟擲InvalidClassException異常。
陣列型別不能明確指定serialVersionUID,所以它們使用預設的計算值,但是反序列化的時候不需要校驗serialVersionUID。