溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java對(duì)象為啥要實(shí)現(xiàn)Serializable接口?

發(fā)布時(shí)間:2020-08-07 21:38:16 來(lái)源:網(wǎng)絡(luò) 閱讀:302 作者:wx5d9ed7c8443c3 欄目:編程語(yǔ)言

Java對(duì)象為啥要實(shí)現(xiàn)Serializable接口?

導(dǎo)讀

最近這段時(shí)間一直在忙著編寫(xiě)Java業(yè)務(wù)代碼,麻木地搬著Ctrl-C、Ctrl-V的磚,在不知道重復(fù)了多少次定義Java實(shí)體對(duì)象時(shí)“implements Serializable”的C/V大法后,腦海中突然冒出一個(gè)思維(A):?jiǎn)柫俗约阂痪?strong>“Java實(shí)體對(duì)象為什么一定要實(shí)現(xiàn)Serializable接口呢?”,關(guān)于這個(gè)問(wèn)題,腦海中的另一個(gè)思維(B)立馬給出了回復(fù)“居然問(wèn)這么幼稚和基礎(chǔ)的問(wèn)題,實(shí)現(xiàn)Serilizable接口是為了序列化??!”,思維(A):“哦,好吧!然而,然后呢?”

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

Serializable接口概述

Serializable是java.io包中定義的、用于實(shí)現(xiàn)Java類(lèi)的序列化操作而提供的一個(gè)語(yǔ)義級(jí)別的接口。Serializable序列化接口沒(méi)有任何方法或者字段,只是用于標(biāo)識(shí)可序列化的語(yǔ)義。實(shí)現(xiàn)了Serializable接口的類(lèi)可以被ObjectOutputStream轉(zhuǎn)換為字節(jié)流,同時(shí)也可以通過(guò)ObjectInputStream再將其解析為對(duì)象。例如,我們可以將序列化對(duì)象寫(xiě)入文件后,再次從文件中讀取它并反序列化成對(duì)象,也就是說(shuō),可以使用表示對(duì)象及其數(shù)據(jù)的類(lèi)型信息和字節(jié)在內(nèi)存中重新創(chuàng)建對(duì)象。

而這一點(diǎn)對(duì)于面向?qū)ο蟮木幊陶Z(yǔ)言來(lái)說(shuō)是非常重要的,因?yàn)闊o(wú)論什么編程語(yǔ)言,其底層涉及IO操作的部分還是由操作系統(tǒng)其幫其完成的,而底層IO操作都是以字節(jié)流的方式進(jìn)行的,所以寫(xiě)操作都涉及將編程語(yǔ)言數(shù)據(jù)類(lèi)型轉(zhuǎn)換為字節(jié)流,而讀操作則又涉及將字節(jié)流轉(zhuǎn)化為編程語(yǔ)言類(lèi)型的特定數(shù)據(jù)類(lèi)型。而Java作為一門(mén)面向?qū)ο蟮木幊陶Z(yǔ)言,對(duì)象作為其主要數(shù)據(jù)的類(lèi)型載體,為了完成對(duì)象數(shù)據(jù)的讀寫(xiě)操作,也就需要一種方式來(lái)讓JVM知道在進(jìn)行IO操作時(shí)如何將對(duì)象數(shù)據(jù)轉(zhuǎn)換為字節(jié)流,以及如何將字節(jié)流數(shù)據(jù)轉(zhuǎn)換為特定的對(duì)象,而Serializable接口就承擔(dān)了這樣一個(gè)角色。

下面我們可以通過(guò)例子來(lái)實(shí)現(xiàn)將序列化的對(duì)象存儲(chǔ)到文件,然后再將其從文件中反序列化為對(duì)象,代碼示例如下:

先定義一個(gè)序列化對(duì)象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;
    }
}

然后我們編寫(xiě)測(cè)試類(lèi),來(lái)對(duì)該對(duì)象進(jìn)行讀寫(xiě)操作,我們先測(cè)試將該對(duì)象寫(xiě)入一個(gè)文件:

public class SerializableTest {

    /**
     * 將User對(duì)象作為文本寫(xiě)入磁盤(pán)
     */
    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();
    }
}

運(yùn)行上述代碼,我們就將User對(duì)象及其攜帶的數(shù)據(jù)寫(xiě)入了文本user.txt中,我們可以看下user.txt中存儲(chǔ)的數(shù)據(jù)此時(shí)是個(gè)什么格式:

Java對(duì)象為啥要實(shí)現(xiàn)Serializable接口?

我們看到對(duì)象數(shù)據(jù)以二進(jìn)制文本的方式被持久化到了磁盤(pán)文件中。在進(jìn)行反序列化測(cè)試之前,我們可以嘗試下將User實(shí)現(xiàn)Serializable接口的代碼部分去掉,看看此時(shí)寫(xiě)操作是否還能成功,結(jié)果如下:

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)

結(jié)果不出所料,果然是不可以的,拋出了NotSerializableException異常,提示非可序列化異常,也就是說(shuō)沒(méi)有實(shí)現(xiàn)Serializable接口的對(duì)象是無(wú)法通過(guò)IO操作持久化的。

接下來(lái),我們繼續(xù)編寫(xiě)測(cè)試代碼,嘗試將之前持久化寫(xiě)入user.txt文件的對(duì)象數(shù)據(jù)再次轉(zhuǎn)化為Java對(duì)象,代碼如下:

public class SerializableTest {
    /**
     * 將類(lèi)從文本中提取并賦值給內(nèi)存中的類(lèi)
     */
    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();
    }
}

通過(guò)反序列化操作,可以再次將持久化的對(duì)象字節(jié)流數(shù)據(jù)通過(guò)IO轉(zhuǎn)化為Java對(duì)象,結(jié)果如下:

cn.wudimanong.serializable.User@6f496d9f

此時(shí),如果我們?cè)俅螄L試將User實(shí)現(xiàn)Serializable接口的代碼部分去掉,發(fā)現(xiàn)也無(wú)法再文本轉(zhuǎn)換為序列化對(duì)象,報(bào)錯(cuò)信息為:

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

提示非法類(lèi)型轉(zhuǎn)換異常,說(shuō)明在Java中如何要實(shí)現(xiàn)對(duì)象的IO讀寫(xiě)操作,都必須實(shí)現(xiàn)Serializable接口,否則代碼就會(huì)報(bào)錯(cuò)!

序列化&反序列化

通過(guò)上面的闡述和示例,相信大家對(duì)Serializable接口的作用是有了比較具體的體會(huì)了,接下來(lái)我們上層到理論層面,看下到底什么是序列化/反序列化。序列化是指把對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程,我們稱之為對(duì)象的序列化,就是把內(nèi)存中的這些對(duì)象變成一連串的字節(jié)(bytes)描述的過(guò)程。

而反序列化則相反,就是把持久化的字節(jié)文件數(shù)據(jù)恢復(fù)為對(duì)象的過(guò)程。那么什么情況下需要序列化呢?大概有這樣兩類(lèi)比較常見(jiàn)的場(chǎng)景:1)、需要把內(nèi)存中的對(duì)象狀態(tài)數(shù)據(jù)保存到一個(gè)文件或者數(shù)據(jù)庫(kù)中的時(shí)候,這個(gè)場(chǎng)景是比較常見(jiàn)的,例如我們利用mybatis框架編寫(xiě)持久層insert對(duì)象數(shù)據(jù)到數(shù)據(jù)庫(kù)中時(shí);2)、網(wǎng)絡(luò)通信時(shí)需要用套接字在網(wǎng)絡(luò)中傳送對(duì)象時(shí),如我們使用RPC協(xié)議進(jìn)行網(wǎng)絡(luò)通信時(shí);

關(guān)于serialVersionUID

對(duì)于JVM來(lái)說(shuō),要進(jìn)行持久化的類(lèi)必須要有一個(gè)標(biāo)記,只有持有這個(gè)標(biāo)記JVM才允許類(lèi)創(chuàng)建的對(duì)象可以通過(guò)其IO系統(tǒng)轉(zhuǎn)換為字節(jié)數(shù)據(jù),從而實(shí)現(xiàn)持久化,而這個(gè)標(biāo)記就是Serializable接口。而在反序列化的過(guò)程中則需要使用serialVersionUID來(lái)確定由那個(gè)類(lèi)來(lái)加載這個(gè)對(duì)象,所以我們?cè)趯?shí)現(xiàn)Serializable接口的時(shí)候,一般還會(huì)要去盡量顯示地定義serialVersionUID,如:

private?static?final?long?serialVersionUID?=?1L;

在反序列化的過(guò)程中,如果接收方為對(duì)象加載了一個(gè)類(lèi),如果該對(duì)象的serialVersionUID與對(duì)應(yīng)持久化時(shí)的類(lèi)不同,那么反序列化的過(guò)程中將會(huì)導(dǎo)致InvalidClassException異常。例如,在之前反序列化的例子中,我們故意將User類(lèi)的serialVersionUID改為2L,如:

private?static?final?long?serialVersionUID?=?2L;

那么此時(shí),在反序例化時(shí)就會(huì)導(dǎo)致異常,如下:

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)

如果我們?cè)谛蛄谢袥](méi)有顯示地聲明serialVersionUID,則序列化運(yùn)行時(shí)將會(huì)根據(jù)該類(lèi)的各個(gè)方面計(jì)算該類(lèi)默認(rèn)的serialVersionUID值。但是,Java官方強(qiáng)烈建議所有要序列化的類(lèi)都顯示地聲明serialVersionUID字段,因?yàn)槿绻叨纫蕾囉贘VM默認(rèn)生成serialVersionUID,可能會(huì)導(dǎo)致其與編譯器的實(shí)現(xiàn)細(xì)節(jié)耦合,這樣可能會(huì)導(dǎo)致在反序列化的過(guò)程中發(fā)生意外的InvalidClassException異常。因此,為了保證跨不同Java編譯器實(shí)現(xiàn)的serialVersionUID值的一致,實(shí)現(xiàn)Serializable接口的必須顯示地聲明serialVersionUID字段。

此外serialVersionUID字段地聲明要盡可能使用private關(guān)鍵字修飾,這是因?yàn)樵撟侄蔚穆暶髦贿m用于聲明的類(lèi),該字段作為成員變量被子類(lèi)繼承是沒(méi)有用處的!有個(gè)特殊的地方需要注意的是,數(shù)組類(lèi)是不能顯示地聲明serialVersionUID的,因?yàn)樗鼈兪冀K具有默認(rèn)計(jì)算的值,不過(guò)數(shù)組類(lèi)反序列化過(guò)程中也是放棄了匹配serialVersionUID值的要求。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI