Java對象爲啥要實現Serializable接口?

Java對象爲啥要實現Serializable接口?



最近這段時間一直在忙着編寫Java業務代碼,麻木地搬着Ctrl-C、Ctrl-V的磚,在不知道重複了多少次定義Java實體對象時「implements Serializable」的C/V大法後,腦海中忽然冒出一個思惟(A):問了本身一句「Java實體對象爲何必定要實現Serializable接口呢?」,關於這個問題,腦海中的另外一個思惟(B)立馬給出了回覆「竟然問這麼幼稚和基礎的問題,實現Serilizable接口是爲了序列化啊!」,思惟(A):「哦,好吧!然而,而後呢?」html

此時思惟(B)陷入了沉默,忽然感受本身有點淺薄了,好像寫了這麼多年Java還真是沒有太關注過Serializable這個接口!爲何必定要實現Serializable接口?它的底層原理是什麼?爲何必定要序列化,序列化又是什麼?關於這些問題,不知道各位讀者朋友有沒有過相似的問題,若是有那麼咱們就在這篇文章中一塊兒尋找答案吧!固然,若是你對這些問題都很清楚,也歡迎表達見解!java

Serializable接口概述

Serializable是http://java.io包中定義的、用於實現Java類的序列化操做而提供的一個語義級別的接口。Serializable序列化接口沒有任何方法或者字段,只是用於標識可序列化的語義。實現了Serializable接口的類能夠被ObjectOutputStream轉換爲字節流,同時也能夠經過ObjectInputStream再將其解析爲對象。例如,咱們能夠將序列化對象寫入文件後,再次從文件中讀取它並反序列化成對象,也就是說,可使用表示對象及其數據的類型信息和字節在內存中從新建立對象。數據庫

而這一點對於面向對象的編程語言來講是很是重要的,由於不管什麼編程語言,其底層涉及IO操做的部分仍是由操做系統其幫其完成的,而底層IO操做都是以字節流的方式進行的,因此寫操做都涉及將編程語言數據類型轉換爲字節流,而讀操做則又涉及將字節流轉化爲編程語言類型的特定數據類型。而Java做爲一門面向對象的編程語言,對象做爲其主要數據的類型載體,爲了完成對象數據的讀寫操做,也就須要一種方式來讓JVM知道在進行IO操做時如何將對象數據轉換爲字節流,以及如何將字節流數據轉換爲特定的對象,而Serializable接口就承擔了這樣一個角色。編程

下面咱們能夠經過例子來實現將序列化的對象存儲到文件,而後再將其從文件中反序列化爲對象,代碼示例以下:api

先定義一個序列化對象User:數組

public class User implements Serializable { 
    private static final long serialVersionUID = 1L; 
 
    private String userId; 
    private String userName; 
 
    public User(String userId, String userName) { 
        this.userId = userId; 
        this.userName = userName; 
    } }

而後咱們編寫測試類,來對該對象進行讀寫操做,咱們先測試將該對象寫入一個文件:網絡

public class SerializableTest { 
 
    /** 
     * 將User對象做爲文本寫入磁盤 
     */ 
    public static void writeObj() { 
        User user = new User("1001", "Joe"); 
        try { 
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt")); 
            objectOutputStream.writeObject(user); 
            objectOutputStream.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    public static void main(String args[]) { 
        writeObj(); 
    } }

運行上述代碼,咱們就將User對象及其攜帶的數據寫入了文本user.txt中,咱們能夠看下user.txt中存儲的數據此時是個什麼格式:mybatis

java.io.NotSerializableException: cn.wudimanong.serializable.User 
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) 
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) 
    at cn.wudimanong.serializable.SerializableTest.writeObj(SerializableTest.java:19) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:27)

咱們看到對象數據以二進制文本的方式被持久化到了磁盤文件中。在進行反序列化測試以前,咱們能夠嘗試下將User實現Serializable接口的代碼部分去掉,看看此時寫操做是否還能成功,結果以下:oracle

結果不出所料,果真是不能夠的,拋出了NotSerializableException異常,提示非可序列化異常,也就是說沒有實現Serializable接口的對象是沒法經過IO操做持久化的。框架

接下來,咱們繼續編寫測試代碼,嘗試將以前持久化寫入user.txt文件的對象數據再次轉化爲Java對象,代碼以下:

public class SerializableTest { 
    /** 
     * 將類從文本中提取並賦值給內存中的類 
     */ 
    public static void readObj() { 
        try { 
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt")); 
            try { 
                Object object = objectInputStream.readObject(); 
                User user = (User) object; 
                System.out.println(user); 
            } catch (ClassNotFoundException e) { 
                e.printStackTrace(); 
            } 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
 
    public static void main(String args[]) { 
        readObj(); 
    } }

經過反序列化操做,能夠再次將持久化的對象字節流數據經過IO轉化爲Java對象,結果以下:

cn.wudimanong.serializable.User@6f496d9f

此時,若是咱們再次嘗試將User實現Serializable接口的代碼部分去掉,發現也沒法再文本轉換爲序列化對象,報錯信息爲:

ava.io.InvalidClassException: cn.wudimanong.serializable.User; class invalid for deserialization 
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157) 
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2038) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568) 
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428) 
    at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

提示非法類型轉換異常,說明在Java中如何要實現對象的IO讀寫操做,都必須實現Serializable接口,不然代碼就會報錯!

序列化&反序列化

經過上面的闡述和示例,相信你們對Serializable接口的做用是有了比較具體的體會了,接下來咱們上層到理論層面,看下到底什麼是序列化/反序列化。序列化是指把對象轉換爲字節序列的過程,咱們稱之爲對象的序列化,就是把內存中的這些對象變成一連串的字節(bytes)描述的過程。

而反序列化則相反,就是把持久化的字節文件數據恢復爲對象的過程。那麼什麼狀況下須要序列化呢?大概有這樣兩類比較常見的場景:1)、須要把內存中的對象狀態數據保存到一個文件或者數據庫中的時候,這個場景是比較常見的,例如咱們利用mybatis框架編寫持久層insert對象數據到數據庫中時;2)、網絡通訊時須要用套接字在網絡中傳送對象時,如咱們使用RPC協議進行網絡通訊時;

關於serialVersionUID

對於JVM來講,要進行持久化的類必需要有一個標記,只有持有這個標記JVM才容許類建立的對象能夠經過其IO系統轉換爲字節數據,從而實現持久化,而這個標記就是Serializable接口。而在反序列化的過程當中則須要使用serialVersionUID來肯定由那個類來加載這個對象,因此咱們在實現Serializable接口的時候,通常還會要去儘可能顯示地定義serialVersionUID,如:

private static final long serialVersionUID = 1L;

在反序列化的過程當中,若是接收方爲對象加載了一個類,若是該對象的serialVersionUID與對應持久化時的類不一樣,那麼反序列化的過程當中將會致使InvalidClassException異常。例如,在以前反序列化的例子中,咱們故意將User類的serialVersionUID改成2L,如:

private static final long serialVersionUID = 2L;

那麼此時,在反序例化時就會致使異常,以下:

java.io.InvalidClassException: cn.wudimanong.serializable.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687) 
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880) 
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568) 
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428) 
    at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

若是咱們在序列化中沒有顯示地聲明serialVersionUID,則序列化運行時將會根據該類的各個方面計算該類默認的serialVersionUID值。可是,Java官方強烈建議全部要序列化的類都顯示地聲明serialVersionUID字段,由於若是高度依賴於JVM默認生成serialVersionUID,可能會致使其與編譯器的實現細節耦合,這樣可能會致使在反序列化的過程當中發生意外的InvalidClassException異常。所以,爲了保證跨不一樣Java編譯器實現的serialVersionUID值的一致,實現Serializable接口的必須顯示地聲明serialVersionUID字段。

此外serialVersionUID字段地聲明要儘量使用private關鍵字修飾,這是由於該字段的聲明只適用於聲明的類,該字段做爲成員變量被子類繼承是沒有用處的!有個特殊的地方須要注意的是,數組類是不能顯示地聲明serialVersionUID的,由於它們始終具備默認計算的值,不過數組類反序列化過程當中也是放棄了匹配serialVersionUID值的要求。

參考資料:

https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html?is-external=true

http://www.tutorialspoint.com/java/java_serialization.htm

相關文章
相關標籤/搜索