Java對象Serializable接口實現(xiàn)詳解
這篇文章主要介紹了Java對象Serializable接口實現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
導(dǎo)讀
最近這段時間一直在忙著編寫Java業(yè)務(wù)代碼,麻木地搬著Ctrl-C、Ctrl-V的磚,在不知道重復(fù)了多少次定義Java實體對象時“implements Serializable”的C/V大法后,腦海中突然冒出一個思維(A):問了自己一句“Java實體對象為什么一定要實現(xiàn)Serializable接口呢?”,關(guān)于這個問題,腦海中的另一個思維(B)立馬給出了回復(fù)“居然問這么幼稚和基礎(chǔ)的問題,實現(xiàn)Serilizable接口是為了序列化??!”,思維(A):“哦,好吧!然而,然后呢?”
此時思維(B)陷入了沉默,突然感覺自己有點淺薄了,好像寫了這么多年Java還真是沒有太關(guān)注過Serializable這個接口!為什么一定要實現(xiàn)Serializable接口?它的底層原理是什么?為什么一定要序列化,序列化又是什么?關(guān)于這些問題,不知道各位讀者朋友有沒有過類似的問題,如果有那么我們就在這篇文章中一起尋找答案吧!當(dāng)然,如果你對這些問題都很清楚,也歡迎表達(dá)看法!
Serializable接口概述
Serializable是java.io包中定義的、用于實現(xiàn)Java類的序列化操作而提供的一個語義級別的接口。Serializable序列化接口沒有任何方法或者字段,只是用于標(biāo)識可序列化的語義。實現(xiàn)了Serializable接口的類可以被ObjectOutputStream轉(zhuǎn)換為字節(jié)流,同時也可以通過ObjectInputStream再將其解析為對象。例如,我們可以將序列化對象寫入文件后,再次從文件中讀取它并反序列化成對象,也就是說,可以使用表示對象及其數(shù)據(jù)的類型信息和字節(jié)在內(nèi)存中重新創(chuàng)建對象。
而這一點對于面向?qū)ο蟮木幊陶Z言來說是非常重要的,因為無論什么編程語言,其底層涉及IO操作的部分還是由操作系統(tǒng)其幫其完成的,而底層IO操作都是以字節(jié)流的方式進(jìn)行的,所以寫操作都涉及將編程語言數(shù)據(jù)類型轉(zhuǎn)換為字節(jié)流,而讀操作則又涉及將字節(jié)流轉(zhuǎn)化為編程語言類型的特定數(shù)據(jù)類型。而Java作為一門面向?qū)ο蟮木幊陶Z言,對象作為其主要數(shù)據(jù)的類型載體,為了完成對象數(shù)據(jù)的讀寫操作,也就需要一種方式來讓JVM知道在進(jìn)行IO操作時如何將對象數(shù)據(jù)轉(zhuǎn)換為字節(jié)流,以及如何將字節(jié)流數(shù)據(jù)轉(zhuǎn)換為特定的對象,而Serializable接口就承擔(dān)了這樣一個角色。
下面我們可以通過例子來實現(xiàn)將序列化的對象存儲到文件,然后再將其從文件中反序列化為對象,代碼示例如下:
先定義一個序列化對象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; } }
然后我們編寫測試類,來對該對象進(jìn)行讀寫操作,我們先測試將該對象寫入一個文件:
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對象及其攜帶的數(shù)據(jù)寫入了文本user.txt中,我們可以看下user.txt中存儲的數(shù)據(jù)此時是個什么格式:
我們看到對象數(shù)據(jù)以二進(jìn)制文本的方式被持久化到了磁盤文件中。在進(jìn)行反序列化測試之前,我們可以嘗試下將User實現(xiàn)Serializable接口的代碼部分去掉,看看此時寫操作是否還能成功,結(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異常,提示非可序列化異常,也就是說沒有實現(xiàn)Serializable接口的對象是無法通過IO操作持久化的。
接下來,我們繼續(xù)編寫測試代碼,嘗試將之前持久化寫入user.txt文件的對象數(shù)據(jù)再次轉(zhuǎn)化為Java對象,代碼如下:
public class SerializableTest { /** * 將類從文本中提取并賦值給內(nè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(); } }
通過反序列化操作,可以再次將持久化的對象字節(jié)流數(shù)據(jù)通過IO轉(zhuǎn)化為Java對象,結(jié)果如下:
此時,如果我們再次嘗試將User實現(xiàn)Serializable接口的代碼部分去掉,發(fā)現(xiàn)也無法再文本轉(zhuǎn)換為序列化對象,報錯信息為:
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
提示非法類型轉(zhuǎn)換異常,說明在Java中如何要實現(xiàn)對象的IO讀寫操作,都必須實現(xiàn)Serializable接口,否則代碼就會報錯!
序列化&反序列化
通過上面的闡述和示例,相信大家對Serializable接口的作用是有了比較具體的體會了,接下來我們上層到理論層面,看下到底什么是序列化/反序列化。序列化是指把對象轉(zhuǎn)換為字節(jié)序列的過程,我們稱之為對象的序列化,就是把內(nèi)存中的這些對象變成一連串的字節(jié)(bytes)描述的過程。
而反序列化則相反,就是把持久化的字節(jié)文件數(shù)據(jù)恢復(fù)為對象的過程。那么什么情況下需要序列化呢?大概有這樣兩類比較常見的場景:1)、需要把內(nèi)存中的對象狀態(tài)數(shù)據(jù)保存到一個文件或者數(shù)據(jù)庫中的時候,這個場景是比較常見的,例如我們利用mybatis框架編寫持久層insert對象數(shù)據(jù)到數(shù)據(jù)庫中時;2)、網(wǎng)絡(luò)通信時需要用套接字在網(wǎng)絡(luò)中傳送對象時,如我們使用RPC協(xié)議進(jìn)行網(wǎng)絡(luò)通信時;
關(guān)于serialVersionUID
對于JVM來說,要進(jìn)行持久化的類必須要有一個標(biāo)記,只有持有這個標(biāo)記JVM才允許類創(chuàng)建的對象可以通過其IO系統(tǒng)轉(zhuǎn)換為字節(jié)數(shù)據(jù),從而實現(xiàn)持久化,而這個標(biāo)記就是Serializable接口。而在反序列化的過程中則需要使用serialVersionUID來確定由那個類來加載這個對象,所以我們在實現(xiàn)Serializable接口的時候,一般還會要去盡量顯示地定義serialVersionUID,如:
private static final long serialVersionUID = 1L;
在反序列化的過程中,如果接收方為對象加載了一個類,如果該對象的serialVersionUID與對應(yīng)持久化時的類不同,那么反序列化的過程中將會導(dǎo)致InvalidClassException異常。例如,在之前反序列化的例子中,我們故意將User類的serialVersionUID改為2L,如:
private static final long serialVersionUID = 2L;
那么此時,在反序例化時就會導(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)
如果我們在序列化中沒有顯示地聲明serialVersionUID,則序列化運行時將會根據(jù)該類的各個方面計算該類默認(rèn)的serialVersionUID值。但是,Java官方強烈建議所有要序列化的類都顯示地聲明serialVersionUID字段,因為如果高度依賴于JVM默認(rèn)生成serialVersionUID,可能會導(dǎo)致其與編譯器的實現(xiàn)細(xì)節(jié)耦合,這樣可能會導(dǎo)致在反序列化的過程中發(fā)生意外的InvalidClassException異常。
因此,為了保證跨不同Java編譯器實現(xiàn)的serialVersionUID值的一致,實現(xiàn)Serializable接口的必須顯示地聲明serialVersionUID字段。
此外serialVersionUID字段地聲明要盡可能使用private關(guān)鍵字修飾,這是因為該字段的聲明只適用于聲明的類,該字段作為成員變量被子類繼承是沒有用處的!有個特殊的地方需要注意的是,數(shù)組類是不能顯示地聲明serialVersionUID的,因為它們始終具有默認(rèn)計算的值,不過數(shù)組類反序列化過程中也是放棄了匹配serialVersionUID值的要求。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot執(zhí)行異步任務(wù)Async介紹
這篇文章主要為大家介紹了SpringBoot執(zhí)行異步任務(wù)Async示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09如何使用Spring Cloud Feign日志查看請求響應(yīng)
這篇文章主要介紹了如何使用Spring Cloud Feign日志查看請求響應(yīng),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02詳解spring security 配置多個AuthenticationProvider
這篇文章主要介紹了詳解spring security 配置多個AuthenticationProvider ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05