1. 程式人生 > >Serializable介面與serialVersionUID

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。