序列化與反序列化,使用中千萬(wàn)要避開(kāi)這些坑!
來(lái)源:liuchenyang0515.blog.csdn.net/
article/details/118463573
文章目錄
序列化與反序列化的概念 子類(lèi)實(shí)現(xiàn)Serializable接口,父類(lèi)沒(méi)有實(shí)現(xiàn),子類(lèi)可以序列化嗎? 類(lèi)中存在引用對(duì)象,這個(gè)類(lèi)對(duì)象在什么情況下可以實(shí)現(xiàn)序列化? 同一個(gè)對(duì)象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?
1.序列化與反序列化的概念
先說(shuō)說(shuō)序列化和反序列化的概念
序列化:將對(duì)象寫(xiě)入到IO流中 反序列化:從IO流中恢復(fù)對(duì)象
Serializable接口是一個(gè)標(biāo)記接口,不用實(shí)現(xiàn)任何方法,標(biāo)記當(dāng)前類(lèi)對(duì)象是可以序列化的,是給JVM看的。
序列化機(jī)制允許將這些實(shí)現(xiàn)序列化接口的對(duì)象轉(zhuǎn)化為字節(jié)序列,這些字節(jié)序列可以保證在磁盤(pán)上或者網(wǎng)絡(luò)傳輸后恢復(fù)成原來(lái)的對(duì)象。序列化就是把對(duì)象存儲(chǔ)在JVM以外的地方,序列化機(jī)制可以讓對(duì)象脫離程序的運(yùn)行而獨(dú)立存在。
序列化在業(yè)務(wù)代碼也許用的不多,但是在框架層面用的是很多的。
先給出序列化的例子,請(qǐng)記住這個(gè)People類(lèi),后面會(huì)根據(jù)這個(gè)類(lèi)來(lái)改造講解。
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 對(duì)象</h1>
*/
private static void testSerializablePeople() throws Exception {
// 序列化的步驟
// 用于存儲(chǔ)序列化的文件,這里的java_下劃線(xiàn)僅僅為了說(shuō)明是java序列化對(duì)象,沒(méi)有任何其他含義
File file = new File("/tmp/people_10.java_");
if (!file.exists()) {
// 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
file.getParentFile().mkdirs();
try {
// 2,再創(chuàng)建文件
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
People p = new People(10L);
// 創(chuàng)建一個(gè)輸出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file)
);
// 輸出可序列化對(duì)象
oos.writeObject(p);
// 關(guān)閉輸出流
oos.close();
// 反序列化的步驟
// 創(chuàng)建一個(gè)輸入流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file)
);
// 得到反序列化的對(duì)象,這里可以強(qiáng)轉(zhuǎn)為People類(lèi)型
Object newPerson = ois.readObject();
// 關(guān)閉輸入流
ois.close();
System.out.println(newPerson);
}
public static void main(String[] args) throws Exception {
testSerializablePeople();
}
}
運(yùn)行之后,看到磁盤(pán)文件因?yàn)樾蛄谢嗔艘粋€(gè)文件

控制臺(tái)中因反序列化輸出的對(duì)象信息打印如下:

2.子類(lèi)實(shí)現(xiàn)Serializable接口,父類(lèi)沒(méi)有實(shí)現(xiàn),子類(lèi)可以序列化嗎?
去掉父類(lèi)People的implements Serializable,讓父類(lèi)不實(shí)現(xiàn)序列化接口,子類(lèi)Worker實(shí)現(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>子類(lèi)實(shí)現(xiàn)序列化, 父類(lèi)不實(shí)現(xiàn)序列化</h2>
* */
private static void testSerizableWorker() throws Exception {
File file = new File("/tmp/worker_10.java_");
if (!file.exists()) {
// 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
file.getParentFile().mkdirs();
try {
// 2,再創(chuàng)建文件
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Worker p = new Worker(10L, "lcy", 18);
// 創(chuàng)建一個(gè)輸出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file)
);
// 輸出可序列化對(duì)象
oos.writeObject(p);
// 關(guān)閉輸出流
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object newWorker = ois.readObject(); // 父類(lèi)沒(méi)有序列化的時(shí)候,需要調(diào)用父類(lèi)的無(wú)參數(shù)構(gòu)造方法
ois.close();
System.out.println(newWorker);
}
再次測(cè)試運(yùn)行

我們?cè)诟割?lèi)People中加上空的構(gòu)造方法之后再次執(zhí)行

結(jié)果卻發(fā)現(xiàn)打印的不是Worker,而是父類(lèi)People,因?yàn)樽宇?lèi)沒(méi)有實(shí)現(xiàn)toString而調(diào)用父類(lèi)的toString,所以打印了People對(duì)象,至于父類(lèi)成員變量id為什么是null,原因如下:
1、一個(gè)子類(lèi)實(shí)現(xiàn)了 Serializable 接口,它的父類(lèi)都沒(méi)有實(shí)現(xiàn) Serializable接口,序列化該子類(lèi)對(duì)象。要想反序列化后輸出父類(lèi)定義的某變量的數(shù)值,就需要讓父類(lèi)也實(shí)現(xiàn)Serializable接口或者父類(lèi)有默認(rèn)的無(wú)參的構(gòu)造函數(shù)。
3、根據(jù)以上特性,我們可以將不需要被序列化的字段抽取出來(lái)放到父類(lèi)中,子類(lèi)實(shí)現(xiàn) Serializable接口,父類(lèi)不實(shí)現(xiàn)Serializable接口但提供一個(gè)空構(gòu)造方法,則父類(lèi)的字段數(shù)據(jù)將不被序列化。
最后加上子類(lèi)Worker的toString方法,打印結(jié)果如下:

子類(lèi)實(shí)現(xiàn)Serializable接口,父類(lèi)沒(méi)有實(shí)現(xiàn),子類(lèi)可以序列化??! 這種情況父類(lèi)一定要提供空構(gòu)造方法,不要忘了子類(lèi)的toString方法!
3.類(lèi)中存在引用對(duì)象,這個(gè)類(lèi)對(duì)象在什么情況下可以實(shí)現(xiàn)序列化?
來(lái)一個(gè)組合對(duì)象,里面引用People對(duì)象,此時(shí)People對(duì)象沒(méi)有實(shí)現(xiàn)Serializable接口,能否序列化呢?代碼給出來(lái),大家可以自行復(fù)制測(cè)試一下。
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,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
file.getParentFile().mkdirs();
try {
// 2,再創(chuàng)建文件
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Combo p = new Combo(1, new People(10L));
// 創(chuàng)建一個(gè)輸出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file)
);
// 輸出可序列化對(duì)象
oos.writeObject(p);
// 關(guān)閉輸出流
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();
}
運(yùn)行結(jié)果如下

直接爆出異常,說(shuō)明People類(lèi)沒(méi)有序列化。
當(dāng)People加上implements Serializable實(shí)現(xiàn)序列化接口后,再次執(zhí)行如下

總結(jié):
一個(gè)類(lèi)里面所有的屬性必須是可序列化的,這個(gè)類(lèi)才能順利的序列化。
比如,類(lèi)中存在引用對(duì)象,那么這個(gè)引用對(duì)象必須是可序列化的,這個(gè)類(lèi)才能序列化。另外,關(guān)注互聯(lián)網(wǎng)架構(gòu)師公眾號(hào),回復(fù)“2T”,送你一份面試題寶典!
4.同一個(gè)對(duì)象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?
下面例子中People是可序列化的,每次序列化之前都會(huì)把People的id值修改了,用來(lái)觀察看看,多次序列化期間,如果對(duì)象屬性更新,是否會(huì)影響序列化,反序列化有什么區(qū)別。
/**
* <h2>同一個(gè)對(duì)象多次序列化的問(wèn)題, 坑</h2>
* */
private static void sameObjectRepeatedSerialization() throws Exception {
File file = new File("/tmp/peopele_more.java_");
if (!file.exists()) {
// 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
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();
}
運(yùn)行結(jié)果如下


序列化一次后,后續(xù)繼續(xù)序列化并未重復(fù)轉(zhuǎn)換為字節(jié)序列,而是輸出字符q~
當(dāng)?shù)谝淮涡蛄谢?,不管如何修改這個(gè)對(duì)象的屬性,都不會(huì)對(duì)后續(xù)的序列化產(chǎn)生影響,反序列化的結(jié)果都和第一次相同。
最后,關(guān)注公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師,在后臺(tái)回復(fù):2T,可以獲取我整理和創(chuàng)作的 Java 系列教程非常齊全。
1、2019 年 9 月全國(guó)程序員工資統(tǒng)計(jì),你是什么水平?
3、從零開(kāi)始搭建創(chuàng)業(yè)公司后臺(tái)技術(shù)棧
5、37歲程序員被裁,120天沒(méi)找到工作,無(wú)奈去小公司,結(jié)果懵了...
6、滴滴業(yè)務(wù)中臺(tái)構(gòu)建實(shí)踐,首次曝光
