理解Java序列化

什么是序列化

Java序列化其實是由Java序列接口提供框架將對象轉(zhuǎn)換成字節(jié)序列,而反序列則是將字節(jié)序列重新轉(zhuǎn)換成對象的過程。

序列化本身其實是一個“持久化”的過程,通常情況下Java的對象都存活在內(nèi)存中,而序列化后可以以字節(jié)序列的形式保存在物理存儲設(shè)備上。

如何實現(xiàn)序列化呢

通常情況下一個類只要實現(xiàn)了Serializable接口,那么它的對象就可以被序列化,生成默認的序列化格式。例如對son 類實現(xiàn)序列化的定義聲明:
public class son implements Serializable
下面是son類,只有一個變量,在默認構(gòu)造參數(shù)中被賦值,并且輸出一句類名到控制臺上:

public class son implements Serializable{
    int b;
    public son(){
        b=2222;
        System.out.println("son");
    }
}

再測試的主函數(shù)中,我們創(chuàng)建要保存的文件,打開文件輸入/輸出流,然后用對象輸入/輸出流把它包裝起來,接下來就可以通過調(diào)用writeObject/readObject來序列化和反序列化對象:

    public static void main(String[] args) throws Exception {
        File relative= new File("demo/");
        if(!relative.exists()){
            relative.mkdirs();
        }
        File file=new File(relative,"serial.out");
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
//      son son1=new son();
//      ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream(file));
//      oos.writeObject(son1);
//      oos.flush();
//      oos.close();
//      System.out.println("done");
        
        ObjectInputStream ois =new ObjectInputStream(new FileInputStream(file));
        son son2=(son)ois.readObject();
        System.out.println(son2);
        System.out.println(son2.b);
        ois.close();
    }
上面就是簡單的序列化,但是序列化本身還有一些頭疼的事情。有這樣幾點:
  1. 序列化其實是在保存對象的狀態(tài),所以static修飾的靜態(tài)的域不會被保存,static修飾的對象是類的狀態(tài),即使在該對象中被修改過,依然不會被序列化保存。
  2. 在序列化時,不僅會序列化對象本身,還會對該對象引用的其他對象也進行序列化,所以要求這些對象也是可序列對象。
  3. 如果是一個子類實現(xiàn)了序列化接口,但是它繼承的父類沒有實現(xiàn),那么父類不會被序列化,而是在子類被反序列化時,遞歸調(diào)用父類默認的無參構(gòu)造函數(shù)。所以在子類中修改了父類引用的對象值,反序列后,該對象的值不會是修改后的,而是在父類的無參構(gòu)造函數(shù)中賦予的值,如果沒有賦值,則是初始化為默認值。而如果父類中沒有無參構(gòu)造函數(shù),將會拋出異常:InvalidClassException
  4. 序列化ID問題。在進行反序列化時,會對字節(jié)流中的serialVersionUID與本地的實體類的serialVersionUID進行比較,如果一致才能反序列化成功,否則會報錯。而serialVersionUID默認情況下是根據(jù)類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,如果不想根據(jù)編譯來劃分版本,就可以顯示定義一個serialVersionUID來兼容以前編譯的版本:
    private static final long serialVersionUID = 1L;
針對第三個問題這里我們再看一個父類沒有實現(xiàn)序列化的情況:

下面是parent類:

public class parent{
     int a;
    public parent(){
        System.out.println("parent");
        a=111;
    }
}

對son類也稍作修改:

public class son extends parent implements Serializable{
    int b;
    transient int tmp=100;
    public son(){
        a=222;
        b=2222;
        System.out.println("son");
    }
}

父類很簡單,不實現(xiàn)Serializable接口,有一個變量a,在無參構(gòu)造函數(shù)中賦值,并且輸出類名。
在son類中稍作修改,對父類的變量a賦新值。
在son類中還添加了一個tmp變量,被transient修飾,所有被transient修飾的對象都不會被默認序列化,只能自己定義序列化的細節(jié)。(再以后的文章中討論)

main函數(shù),多添加兩句打印輸出語句:

public class Main {
    
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> a=new ArrayList<Integer>();
        File relative= new File("demo/");
        if(!relative.exists()){
            relative.mkdirs();
        }
        
        File file=new File(relative,"serial.txt");
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
//      
//      son son1=new son();
//      ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream(file));
//      oos.writeObject(son1);
//      oos.flush();
//      oos.close();
//      System.out.println("done");
        
        ObjectInputStream ois =new ObjectInputStream(new FileInputStream(file));
        son son2=(son)ois.readObject();
        System.out.println(son2);    //打印對象名
        System.out.println(son2.a);  //打印反序列后引用父類變量的值
        System.out.println(son2.b);
        ois.close();
    }
}
測試結(jié)果

我們看在第一次序列化寫入文件的情況下控制臺的輸出:

parent
son
done

在實例化son類的過程中先遞歸調(diào)用了父類無參構(gòu)造函數(shù),然后才是son類中的構(gòu)造函數(shù),在最后序列化成功后輸出done,程序結(jié)束。

下面是反序列化時的輸出:

parent
son@5c647e05
111
2222

可以看到反序列的過程中首先調(diào)用了父類的無參構(gòu)造函數(shù),接著打印了反序列后的對象名(同程序中如果直接序列化然后立馬反序列,可以看到兩個對象哈希值是不同的,這里沒有比較),然后輸出了父類中的變量a,可以看到值為111,不是在son的構(gòu)造函數(shù)中賦值的222,證明對父類范圍內(nèi)的對象做修改沒有用。最后變量b的輸出正確。

最后

可以看到,序列化和繼承之間有一些矛盾,同時序列化將類中私有域也序列化輸出,破壞了類的封閉性。所以設(shè)計類的時候盡量不實現(xiàn)序列化,同時如果該類會被廣泛繼承,而繼承后的子類可能要序列化,那么該類要提供一個無參構(gòu)造函數(shù),并且在該構(gòu)造函數(shù)中給對象賦值。

并且,默認序列化并不能滿足所有場景和需求,后面的文章我們會認識一下定制序列化的方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 11,195評論 0 24
  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,493評論 0 10
  • 如果你只知道實現(xiàn) Serializable 接口的對象,可以序列化為本地文件。那你最好再閱讀該篇文章,文章對序列化...
    jiangmo閱讀 561評論 0 2
  • 什么是序列化 所謂的序列化,即把java對象以二進制形式保存到內(nèi)存、文件或者進行網(wǎng)絡(luò)傳輸。從二進制的形式恢復(fù)成為j...
    德彪閱讀 739評論 0 0
  • 剛夢見自己結(jié)婚,還沒來得及化眼妝就穿著婚紗坐在方形大廳的角落里,地面平坦的方形大廳一角。 天花板很高,下面坐滿了人...
    了一斯肥閱讀 543評論 1 1

友情鏈接更多精彩內(nèi)容