Java中的序列化機(jī)制詳細(xì)解讀
序列化與反序列化
- 序列化:將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)臄?shù)據(jù)形式(比如二進(jìn)制)的過程。
- 反序列化:與序列化相對(duì),把序列化轉(zhuǎn)換成的可以存儲(chǔ)或傳輸?shù)臄?shù)據(jù)形式轉(zhuǎn)化為對(duì)象的狀態(tài)信息的過程。
java序列化與反序列化
- 序列化:把對(duì)象轉(zhuǎn)換為二進(jìn)制流。
- 反序列化:把二進(jìn)制流數(shù)據(jù)轉(zhuǎn)化為對(duì)象。
使用場(chǎng)景
- 永久保存數(shù)據(jù)。把序列化后的數(shù)據(jù)保存到磁盤等存儲(chǔ)設(shè)備中。使用時(shí)可以反序列化。比如某些對(duì)象不想隨著JVM關(guān)閉而消失,可以序列化到磁盤中。
- 用于網(wǎng)絡(luò)傳輸。把對(duì)象序列化為二進(jìn)制數(shù)據(jù),傳輸?shù)竭h(yuǎn)程計(jì)算機(jī)。
- java序列化還可以實(shí)現(xiàn)對(duì)象深拷貝。
使用方式
java實(shí)現(xiàn)序列化與反序列化需要ObjectInputStrem(反序列化)和ObjectOutputStream(序列化)。并且序列化對(duì)象需要實(shí)現(xiàn)Serializable接口。
代碼:
//實(shí)現(xiàn)Serializable接口
public class Person implements Serializable{
private static final long serialVersionUID = 2063917965595163411L;
private String name;
private Integer age;
private String Sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return Sex;
}
public void setSex(String sex) {
Sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", Sex='" + Sex + '\'' +
'}';
}
}
public class JavaSerializableDemo {
//初始化一個(gè)Person對(duì)象
public static Person initPerson(){
Person person = new Person();
person.setAge(18);
person.setName("Mike");
person.setSex("man");
return person;
}
//序列化
public static Person serialize(){
Person person = initPerson();
//創(chuàng)建一個(gè)文件輸出流,把序列化的數(shù)據(jù)寫到文件中。
//創(chuàng)建一個(gè)ObjectOutputStream 用于序列化對(duì)象,將序列化的數(shù)據(jù)寫到fileOutputStream 流中。
try (FileOutputStream fileOutputStream = new FileOutputStream("person");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);){
//序列化對(duì)象
objectOutputStream.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
return person;
}
//反序列
public static Person deSerialize(){
Person person = null;
//創(chuàng)建一個(gè)文件輸入流,用于讀取序列化文件。
//創(chuàng)建一個(gè)objectInputStream ,用于反序列化對(duì)象,通過讀取fileInputStream 流數(shù)據(jù)進(jìn)行反序列化
try (FileInputStream fileInputStream = new FileInputStream("person");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);){
//反序列化
person = (Person) objectInputStream.readObject();
return person;
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
public static void main(String[] args) {
Person person = serialize();
Person person1 = deSerialize();
System.out.println("person==>" + person);
System.out.println("person1==>" +person1);
//判斷兩個(gè)對(duì)象是否是用一個(gè)對(duì)象
System.out.println("person == person1 ==> " + (person == person1));
//分別修改屬性,看看會(huì)不會(huì)影響另外一個(gè)對(duì)象
person.setAge(20);
person1.setName("john");
System.out.println("person==>" + person);
System.out.println("person1==>" +person1);
}
}結(jié)果:

第一行和第二行對(duì)象的屬性值一樣說明了序列化會(huì)保存對(duì)象的狀態(tài)信息。 第三行返回false,說明java的序列化機(jī)制實(shí)現(xiàn)的是對(duì)象的copy,而不是對(duì)象引用的copy。 第四行和第五行,修改對(duì)象屬性不會(huì)影響另外一個(gè)對(duì)象,也說明了java的序列化機(jī)制實(shí)現(xiàn)的是對(duì)象的copy,而不是對(duì)象引用的copy。
如果被序列化的對(duì)象的類沒有實(shí)現(xiàn)Serializable接口,就會(huì)拋出異常。

serialVersionUID
serialVersionUID,是序列化版本控制UID,其目的是序列化對(duì)象版本控制,有關(guān)各版本反序列化時(shí)是否兼容。如果在新版本中這個(gè)值修改了,新版本就不兼容舊版本,反序列化時(shí)會(huì)拋出InvalidClassException異常。如果修改較小,比如僅僅是增加了一個(gè)屬性,我們希望向下兼容,老版本的數(shù)據(jù)都能保留,那就不用修改;如果我們刪除了一個(gè)屬性,或者更改了類的繼承關(guān)系,必然不兼容舊數(shù)據(jù),這時(shí)就應(yīng)該手動(dòng)更新版本號(hào),即SerialVersionUid。
如果沒有顯示指定該版本號(hào),編譯器會(huì)在編譯類文件時(shí)自動(dòng)幫我們根據(jù)類的信息創(chuàng)建一個(gè)版本號(hào)。如果沒有指定版本號(hào)的情況下修改類文件信息,會(huì)導(dǎo)致編譯器生成的版本id不一樣,在反序列化時(shí)就會(huì)拋出異常。
步驟:
不顯示指定版本id。進(jìn)行序列化操作。修改Person類,比如新增一個(gè)fatherName屬性。然后用之前序列化的文件進(jìn)行反序列化為修改后的Person類。
結(jié)果:

由于Person文件進(jìn)過了修改,編譯器生成的版本id就會(huì)變化。然后就會(huì)導(dǎo)致序列化時(shí)的版本id與反序列化時(shí)的版本id對(duì)不上而拋出異常。
指定版本id后,就算修改文件,也不會(huì)導(dǎo)致反序列化異常。
靜態(tài)變量的序列化
靜態(tài)變量不會(huì)被序列化,也就是序列化時(shí)會(huì)忽略靜態(tài)變量。因?yàn)殪o態(tài)變量是類級(jí)別的屬性,而java序列化機(jī)制保存的是對(duì)象的狀態(tài)信息。
代碼: person新增一個(gè)live的靜態(tài)屬性。

public static void main(String[] args) {
//先序列化
Person person = serialize();
//修改靜態(tài)變量的值
person.live = "宇宙";
//反序列化
Person person1 = deSerialize();
System.out.println(person1.live);
}
結(jié)論: 首先把對(duì)象序列化,如果會(huì)把靜態(tài)變量的值序列化的話,那么反序列化出來的對(duì)象的靜態(tài)變量值就應(yīng)該是序列化前的值,也就是地球村。
如果會(huì)忽略靜態(tài)變量的話,那么反序列化出來的對(duì)象的靜態(tài)變量值就應(yīng)該是序列化后修改的值,也就是宇宙。

transient
transient關(guān)鍵字用于修飾成員變量,被修飾的成員變量不會(huì)被java的序列化機(jī)制序列化。所以反序列化時(shí)得到的對(duì)象的該屬性會(huì)使用默認(rèn)值,對(duì)象引用默認(rèn)null,int 默認(rèn)為0等,或者如果該類里面初始化了,就使用初始化的值。
Person加上一個(gè)被transient修飾的屬性aliasName

修改測(cè)試代碼和初始化代碼,初始化aliasName,序列化和反序列化代碼不變:
//初始化一個(gè)Person對(duì)象
public static Person initPerson(){
Person person = new Person();
person.setAge(18);
person.setName("Mike");
person.setSex("man");
person.setAliasName("Handsome Mike");
return person;
}
public static void main(String[] args) {
//先序列化
Person person = serialize();
//反序列化
Person person1 = deSerialize();
//輸出被transient修飾的屬性aliasName
System.out.println(person.getAliasName());
System.out.println(person1.getAliasName());
}
結(jié)果:

反序列化的對(duì)象Person的aliasName屬性為null。表名在序列化時(shí),該屬性被忽略了。
繼承關(guān)系的序列化
序列化某個(gè)類,如果該類有父類且父類沒有實(shí)現(xiàn)Serializable接口,則不會(huì)序列化父類的屬性,反序列化時(shí)父類的屬性會(huì)使用默認(rèn)值或者創(chuàng)建對(duì)象時(shí)就初始化的值。
//person類繼承一個(gè)父類。
//實(shí)現(xiàn)Serializable接口
public class Person extends SupperPerson implements Serializable{
private String name;
private Integer age;
private String Sex;
private transient String aliasName;
public static String live = "地球村";
public String getAliasName() {
return aliasName;
}
public void setAliasName(String aliasName) {
this.aliasName = aliasName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return Sex;
}
public void setSex(String sex) {
Sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", Sex='" + Sex + '\'' +
'}';
}
}
//父類
public class SupperPerson{
private String aaa;
private String bbb;
public String getAaa() {
return aaa;
}
public void setAaa(String aaa) {
this.aaa = aaa;
}
public String getBbb() {
return bbb;
}
public void setBbb(String bbb) {
this.bbb = bbb;
}
}
//初始化:
//初始化一個(gè)Person對(duì)象
public static Person initPerson(){
Person person = new Person();
person.setAge(18);
person.setName("Mike");
person.setSex("man");
person.setAliasName("Handsome Mike");
//初始化父類屬性
person.setAaa("aaaaa");
person.setBbb("bbbbb");
return person;
}
//測(cè)試
public static void main(String[] args) {
//先序列化
Person person = serialize();
//反序列化
Person person1 = deSerialize();
//輸出被transient修飾的屬性aliasName
System.out.println(person.getAaa());
System.out.println(person.getBbb());
System.out.println(person1.getAaa());
System.out.println(person1.getBbb());
}
結(jié)果:

父類的屬性aaa和bbb沒有被序列化保存下來。
如果父類也實(shí)現(xiàn)了Serializable接口,就會(huì)序列化父類的屬性。

其他代碼一樣。 結(jié)果:

反序列化后父類的屬性不再是null。
通常序列化實(shí)現(xiàn)深度克隆
克隆一個(gè)對(duì)象就是保存原對(duì)象的狀態(tài)信息新建一個(gè)對(duì)象,實(shí)現(xiàn)對(duì)象的拷貝,新對(duì)象與原對(duì)象在堆上是兩個(gè)不同的對(duì)象。
public class Student implements Serializable,Cloneable{
private String name;
private Integer age;
private String Sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return Sex;
}
public void setSex(String sex) {
Sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", Sex='" + Sex + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
//實(shí)現(xiàn)父類的clone方法。調(diào)用序列化和反序列化。
return deSerialize(serialize(this));
}
/**
* 序列化返回序列化的字節(jié)數(shù)組
* @param student
* @return
*/
private byte[] serialize(Student student){
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);){
objectOutputStream.writeObject(student);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 通過序列化字節(jié)數(shù)組反序列化成Student對(duì)象。
* @param studentByteArray
* @return
*/
private Student deSerialize(byte[] studentByteArray){
try (ByteArrayInputStream bis = new ByteArrayInputStream(studentByteArray);
ObjectInputStream objectInputStream = new ObjectInputStream(bis);){
Student student = (Student) objectInputStream.readObject();
return student;
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
//測(cè)試
public static void main(String[] args) {
Student student = new Student();
student.setAge(22);
student.setName("Mike");
student.setSex("man");
try {
Student studentCopy = (Student) student.clone();
System.out.println(student);
System.out.println(studentCopy);
System.out.println(student == studentCopy);
System.out.println(student.getName() == studentCopy.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
結(jié)果:

第一行和第二行原對(duì)象與克隆對(duì)象的屬性一致,表示克隆成功。
第三行比較的是原對(duì)象與克隆對(duì)象是否是同一個(gè)對(duì)象,比較對(duì)象的地址,返回false,表名是兩個(gè)不同的對(duì)象。
第四行判斷的是對(duì)象的屬性是否也進(jìn)行了克隆,比較兩屬性對(duì)象的地址,返回false,表示也不是同一個(gè)對(duì)象,達(dá)到了深克隆。
總結(jié)
- 在java中,只要一個(gè)類實(shí)現(xiàn)了java.io.Serializable接口,那么他就可以被序列化。
- 通過ObjectInputStream和ObjectOutputStream對(duì)對(duì)象進(jìn)行序列化和反序列化。
- 對(duì)象能否被反序列化,不僅取決于對(duì)象代碼是否一直不變,還取決于UID。
- 序列化不保存靜態(tài)屬性。
- 要想父類的屬性也進(jìn)行序列化,父類也要實(shí)現(xiàn)java.io.Serializable接口。
- transient關(guān)鍵字控制屬性是否會(huì)被序列化,如果沒有被序列化的屬性反序列化后,會(huì)被設(shè)置成初始值。
- 通過序列化機(jī)制可以實(shí)現(xiàn)對(duì)象深拷貝。
到此這篇關(guān)于Java中的序列化機(jī)制詳細(xì)解讀的文章就介紹到這了,更多相關(guān)Java序列化機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 實(shí)現(xiàn)一個(gè)漢諾塔實(shí)戰(zhàn)練習(xí)
漢諾塔是源于印度一個(gè)古老傳說的益智玩具。大梵天創(chuàng)造世界時(shí)做了三根石柱,在一根柱子上從下往上按大小順序摞著64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。并且規(guī)定,在小圓盤上不能放大圓盤,三根柱子之間一次只能移動(dòng)一個(gè)圓盤2021-10-10
SpringSecurity在分布式環(huán)境下的使用流程分析
文章介紹了Spring?Security在分布式環(huán)境下的使用,包括單點(diǎn)登錄(SSO)的概念、流程圖以及JWT(JSON?Web?Token)的生成和校驗(yàn),通過使用JWT和RSA非對(duì)稱加密,可以實(shí)現(xiàn)安全的分布式認(rèn)證,感興趣的朋友一起看看吧2025-02-02
java 中sendredirect()和forward()方法的區(qū)別
這篇文章主要介紹了java 中sendredirect()和forward()方法的區(qū)別,需要的朋友可以參考下2017-08-08
IDEA創(chuàng)建maven項(xiàng)目時(shí)在tomcat運(yùn)行瀏覽器404的問題
這篇文章主要介紹了IDEA創(chuàng)建maven項(xiàng)目時(shí)在tomcat運(yùn)行瀏覽器404的問題及解決方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
datax-web在windows環(huán)境idea中模塊化打包部署操作步驟
這篇文章主要介紹了datax-web在windows環(huán)境idea中模塊化打包部署操作步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
springboot打包部署到linux服務(wù)器的方法
這篇文章主要介紹了springboot打包部署到linux服務(wù)器的方法,通過實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析
這篇文章主要介紹了SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
SpringBoot集成阿里云短信的實(shí)戰(zhàn)指南
在SpringBoot項(xiàng)目中集成阿里云短信服務(wù),核心是完成一系列前置申請(qǐng),并調(diào)用其官方SDK發(fā)送短信,下面小編就和大家詳細(xì)介紹一下具體的實(shí)現(xiàn)方法吧2026-01-01

