中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2020-08-07 21:38:16 來源:網絡 閱讀:309 作者:wx5d9ed7c8443c3 欄目:編程語言

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

導讀

最近這段時間一直在忙著編寫Java業務代碼,麻木地搬著Ctrl-C、Ctrl-V的磚,在不知道重復了多少次定義Java實體對象時“implements Serializable”的C/V大法后,腦海中突然冒出一個思維(A):問了自己一句Java實體對象為什么一定要實現Serializable接口呢?”,關于這個問題,腦海中的另一個思維(B)立馬給出了回復“居然問這么幼稚和基礎的問題,實現Serilizable接口是為了序列化啊!”,思維(A):“哦,好吧!然而,然后呢?”

此時思維(B)陷入了沉默,突然感覺自己有點淺薄了,好像寫了這么多年Java還真是沒有太關注過Serializable這個接口!為什么一定要實現Serializable接口?它的底層原理是什么?為什么一定要序列化,序列化又是什么?關于這些問題,不知道各位讀者朋友有沒有過類似的問題,如果有那么我們就在這篇文章中一起尋找答案吧!當然,如果你對這些問題都很清楚,也歡迎表達看法!

Serializable接口概述

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

而這一點對于面向對象的編程語言來說是非常重要的,因為無論什么編程語言,其底層涉及IO操作的部分還是由操作系統其幫其完成的,而底層IO操作都是以字節流的方式進行的,所以寫操作都涉及將編程語言數據類型轉換為字節流,而讀操作則又涉及將字節流轉化為編程語言類型的特定數據類型。而Java作為一門面向對象的編程語言,對象作為其主要數據的類型載體,為了完成對象數據的讀寫操作,也就需要一種方式來讓JVM知道在進行IO操作時如何將對象數據轉換為字節流,以及如何將字節流數據轉換為特定的對象,而Serializable接口就承擔了這樣一個角色。

下面我們可以通過例子來實現將序列化的對象存儲到文件,然后再將其從文件中反序列化為對象,代碼示例如下:

先定義一個序列化對象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中存儲的數據此時是個什么格式:

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

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

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)

結果不出所料,果然是不可以的,拋出了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.jav

提示非法類型轉換異常,說明在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值的要求。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

三原县| 夏津县| 长乐市| 南澳县| 沾化县| 乌拉特后旗| 古浪县| 濉溪县| 康马县| 馆陶县| 五华县| 教育| 资溪县| 荔浦县| 姚安县| 屏东市| 福州市| 辽宁省| 赣榆县| 新邵县| 郑州市| 永泰县| 普兰店市| 柘荣县| 潜山县| 唐海县| 伊宁县| 美姑县| 遂宁市| 鸡东县| 博湖县| 勐海县| 沂水县| 静海县| 衡南县| 敦化市| 黑河市| 社旗县| 定襄县| 平阴县| 威远县|