实现序列化与反序列化,一定要绕开这些坑!
今日推薦
文章目錄
序列化與反序列化的概念
子類實現(xiàn)Serializable接口,父類沒有實現(xiàn),子類可以序列化嗎?
類中存在引用對象,這個類對象在什么情況下可以實現(xiàn)序列化?
同一個對象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?
1.序列化與反序列化的概念
先說說序列化和反序列化的概念
序列化:將對象寫入到IO流中
反序列化:從IO流中恢復對象
Serializable接口是一個標記接口,不用實現(xiàn)任何方法,標記當前類對象是可以序列化的,是給JVM看的。
序列化機制允許將這些實現(xiàn)序列化接口的對象轉化為字節(jié)序列,這些字節(jié)序列可以保證在磁盤上或者網(wǎng)絡傳輸后恢復成原來的對象。序列化就是把對象存儲在JVM以外的地方,序列化機制可以讓對象脫離程序的運行而獨立存在。
序列化在業(yè)務代碼也許用的不多,但是在框架層面用的是很多的。
相關技術:Session的序列化或者反序列化
先給出序列化的例子,請記住這個People類,后面會根據(jù)這個類來改造講解。
public?class?People?{??private?Long?id;??public?People(Long?id)?{??this.id?=?id;??}??public?Long?getId()?{??return?id;??}??public?void?setId(Long?id)?{??this.id?=?id;??}??@Override??public?String?toString()?{??return?"People{"?+??"id="?+?id?+??'}';??}?? }import?java.io.*;??//?屏蔽編譯器的警告?? @SuppressWarnings("all")?? public?class?Main?{??/**??*?<h1>序列化和反序列化?People?對象</h1>??*/??private?static?void?testSerializablePeople()?throws?Exception?{??//?序列化的步驟??//?用于存儲序列化的文件,這里的java_下劃線僅僅為了說明是java序列化對象,沒有任何其他含義??File?file?=?new?File("/tmp/people_10.java_");??if?(!file.exists())?{??//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄??file.getParentFile().mkdirs();??try?{??//?2,再創(chuàng)建文件??file.createNewFile();??}?catch?(IOException?e)?{??e.printStackTrace();??}??}??People?p?=?new?People(10L);??//?創(chuàng)建一個輸出流??ObjectOutputStream?oos?=?new?ObjectOutputStream(??new?FileOutputStream(file)??);??//?輸出可序列化對象??oos.writeObject(p);??//?關閉輸出流??oos.close();??//?反序列化的步驟??//?創(chuàng)建一個輸入流??ObjectInputStream?ois?=?new?ObjectInputStream(??new?FileInputStream(file)??);??//?得到反序列化的對象,這里可以強轉為People類型??Object?newPerson?=?ois.readObject();??//?關閉輸入流??ois.close();??System.out.println(newPerson);??}??public?static?void?main(String[]?args)?throws?Exception?{??testSerializablePeople();??}?? }運行之后,看到磁盤文件因為序列化而多了一個文件
圖片控制臺中因反序列化輸出的對象信息打印如下:
圖片2.子類實現(xiàn)Serializable接口,父類沒有實現(xiàn),子類可以序列化嗎?
去掉父類People的implements Serializable,讓父類不實現(xiàn)序列化接口,子類Worker實現(xiàn)序列化接口
public?class?Worker?extends?People?implements?Serializable?{??private?String?name;??private?Integer?age;??public?Worker(Long?id,?String?name,?Integer?age)?{??super(id);??this.name?=?name;??this.age?=?age;??}??}public?static?void?main(String[]?args)?throws?Exception?{??testSerizableWorker();??}??/**??*?<h2>子類實現(xiàn)序列化,?父類不實現(xiàn)序列化</h2>??*?*/??private?static?void?testSerizableWorker()?throws?Exception?{??File?file?=?new?File("/tmp/worker_10.java_");??if?(!file.exists())?{??//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄??file.getParentFile().mkdirs();??try?{??//?2,再創(chuàng)建文件??file.createNewFile();??}?catch?(IOException?e)?{??e.printStackTrace();??}??}??Worker?p?=?new?Worker(10L,?"lcy",?18);??//?創(chuàng)建一個輸出流??ObjectOutputStream?oos?=?new?ObjectOutputStream(??new?FileOutputStream(file)??);??//?輸出可序列化對象??oos.writeObject(p);??//?關閉輸出流??oos.close();??ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));??Object?newWorker?=?ois.readObject();?//?父類沒有序列化的時候,需要調用父類的無參數(shù)構造方法??ois.close();??System.out.println(newWorker);??}再次測試運行
圖片結果顯示沒有有效地構造器,原來是因為父類沒有序列化的時候,Object newWorker = ois.readObject()需要直接調用父類的無參數(shù)構造方法,不經(jīng)過子類的無參構造方法。
我們在父類People中加上空的構造方法之后再次執(zhí)行
圖片結果卻發(fā)現(xiàn)打印的不是Worker,而是父類People,因為子類沒有實現(xiàn)toString而調用父類的toString,所以打印了People對象,至于父類成員變量id為什么是null,原因如下:
1、一個子類實現(xiàn)了 Serializable 接口,它的父類都沒有實現(xiàn) Serializable接口,序列化該子類對象。要想反序列化后輸出父類定義的某變量的數(shù)值,就需要讓父類也實現(xiàn)Serializable接口或者父類有默認的無參的構造函數(shù)。
2、在父類沒有實現(xiàn)Serializable 接口時,虛擬機是不會序列化父對象的,而一個 Java對象的構造必須先有父對象,才有子對象,反序列化也不例外。所以反序列化時,為了構造父對象,只能調用父類的無參構造函數(shù)作為默認的父對象。因此當我們取父對象的變量值時,它的值是調用父類無參構造函數(shù)后的值,如果在父類無參構造函數(shù)中沒有對變量賦值,那么父類成員變量值都是默認值,如這里的Long型就是null。
3、根據(jù)以上特性,我們可以將不需要被序列化的字段抽取出來放到父類中,子類實現(xiàn) Serializable接口,父類不實現(xiàn)Serializable接口但提供一個空構造方法,則父類的字段數(shù)據(jù)將不被序列化。
最后加上子類Worker的toString方法,打印結果如下:
圖片總結:
子類實現(xiàn)Serializable接口,父類沒有實現(xiàn),子類可以序列化!!
這種情況父類一定要提供空構造方法,不要忘了子類的toString方法!
3.類中存在引用對象,這個類對象在什么情況下可以實現(xiàn)序列化?
來一個組合對象,里面引用People對象,此時People對象沒有實現(xiàn)Serializable接口,能否序列化呢?代碼給出來,大家可以自行復制測試一下。
public?class?Combo?implements?Serializable?{??private?int?id;??private?People?people;??public?Combo(int?id,?People?people)?{??this.id?=?id;??this.people?=?people;??}??public?int?getId()?{??return?id;??}??public?void?setId(int?id)?{??this.id?=?id;??}??public?People?getPeople()?{??return?people;??}??public?void?setPeople(People?people)?{??this.people?=?people;??}??@Override??public?String?toString()?{??return?"Combo{"?+??"id="?+?id?+??",?people="?+?people?+??'}';??}?? }public?class?People?{??private?Long?id;??public?People()?{??}??public?People(Long?id)?{??this.id?=?id;??}??public?Long?getId()?{??return?id;??}??public?void?setId(Long?id)?{??this.id?=?id;??}??@Override??public?String?toString()?{??return?"People{"?+??"id="?+?id?+??'}';??}?? }private?static?void?testSerializableCombo()?throws?Exception?{??File?file?=?new?File("/tmp/combo_10.java_");??if?(!file.exists())?{??//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄??file.getParentFile().mkdirs();??try?{??//?2,再創(chuàng)建文件??file.createNewFile();??}?catch?(IOException?e)?{??e.printStackTrace();??}??}??Combo?p?=?new?Combo(1,?new?People(10L));??//?創(chuàng)建一個輸出流??ObjectOutputStream?oos?=?new?ObjectOutputStream(??new?FileOutputStream(file)??);??//?輸出可序列化對象??oos.writeObject(p);??//?關閉輸出流??oos.close();??ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));??Object?newCombo?=?ois.readObject();??ois.close();??System.out.println(newCombo);??}??public?static?void?main(String[]?args)?throws?Exception?{??testSerializableCombo();??}運行結果如下
圖片直接爆出異常,說明People類沒有序列化。
當People加上implements Serializable實現(xiàn)序列化接口后,再次執(zhí)行如下
圖片總結:
一個類里面所有的屬性必須是可序列化的,這個類才能順利的序列化。
比如,類中存在引用對象,那么這個引用對象必須是可序列化的,這個類才能序列化。
4.同一個對象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?
下面例子中People是可序列化的,每次序列化之前都會把People的id值修改了,用來觀察看看,多次序列化期間,如果對象屬性更新,是否會影響序列化,反序列化有什么區(qū)別。
/**??*?<h2>同一個對象多次序列化的問題,?坑</h2>??*?*/?? private?static?void?sameObjectRepeatedSerialization()?throws?Exception?{??File?file?=?new?File("/tmp/peopele_more.java_");??if?(!file.exists())?{??//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄??file.getParentFile().mkdirs();??try?{??//?2,再創(chuàng)建文件??file.createNewFile();??}?catch?(IOException?e)?{??e.printStackTrace();??}??}??People?p?=?new?People(10L);??ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream(file));??//?未序列化,先修改屬性??p.setId(11L);??oos.writeObject(p);??//?序列化一次后,再次修改屬性??p.setId(15L);??oos.writeObject(p);??//?序列化兩次后,再次修改屬性??p.setId(20L);??oos.writeObject(p);??oos.close();??ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));??Object?people1?=?ois.readObject();??Object?people2?=?ois.readObject();??Object?people3?=?ois.readObject();??ois.close();??System.out.println(((People)?people1).getId());??System.out.println(((People)?people2).getId());??System.out.println(((People)?people3).getId());?? }??public?static?void?main(String[]?args)?throws?Exception?{??sameObjectRepeatedSerialization();?? }運行結果如下
圖片結果發(fā)現(xiàn)反序列化讀出的值都是一樣的。說明當對象第一次序列化成功后,后續(xù)這個對象屬性即使有修改,也不會對后面的序列化造成成影響。
這其實是序列化算法的原因,所有要序列化的對象都有一個序列化的編碼號,當試圖序列化一個對象,會檢查這個對象是否已經(jīng)序列化過,若從未序列化過,才會序列化為字節(jié)序列去輸出。若已經(jīng)序列化過,則會輸出一個編碼符號,不會重復序列化一個對象。如下
圖片序列化一次后,后續(xù)繼續(xù)序列化并未重復轉換為字節(jié)序列,而是輸出字符q~
總結:
當?shù)谝淮涡蛄谢?#xff0c;不管如何修改這個對象的屬性,都不會對后續(xù)的序列化產(chǎn)生影響,反序列化的結果都和第一次相同。
感謝閱讀,希望對你有所幫助?:)?
來源:liuchenyang0515.blog.csdn.net/
article/details/118463573
推薦文章1、一款高顏值的 SpringBoot+JPA 博客項目2、超優(yōu) Vue+Element+Spring 中后端解決方案3、推薦幾個支付項目!4、推薦一個 Java 企業(yè)信息化系統(tǒng)5、一款基于 Spring Boot 的現(xiàn)代化社區(qū)(論壇/問答/社交網(wǎng)絡/博客)總結
以上是生活随笔為你收集整理的实现序列化与反序列化,一定要绕开这些坑!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HashMap 面试常见的6连问,你能扛
- 下一篇: 推荐一款基于SpringBoot+Vue