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ì)象。
使用場景
- 永久保存數(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
修改測試代碼和初始化代碼,初始化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; } //測試 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; } } //測試 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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何為SpringBoot項(xiàng)目中的自定義配置添加IDE支持
這篇文章主要介紹了詳解如何為SpringBoot項(xiàng)目中的自定義配置添加IDE支持,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02feign 調(diào)用第三方服務(wù)中部分特殊符號(hào)未轉(zhuǎn)義問題
這篇文章主要介紹了feign 調(diào)用第三方服務(wù)中部分特殊符號(hào)未轉(zhuǎn)義問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Spring Boot+Nginx實(shí)現(xiàn)大文件下載功能
相信很多小伙伴,在日常開放中都會(huì)遇到大文件下載的情況,大文件下載方式也有很多,比如非常流行的分片下載、斷點(diǎn)下載;當(dāng)然也可以結(jié)合Nginx來實(shí)現(xiàn)大文件下載,在中小項(xiàng)目非常適合使用,這篇文章主要介紹了Spring Boot結(jié)合Nginx實(shí)現(xiàn)大文件下載,需要的朋友可以參考下2024-05-05Java Web項(xiàng)目部署在Tomcat運(yùn)行出錯(cuò)與解決方法示例
這篇文章主要介紹了Java Web項(xiàng)目部署在Tomcat運(yùn)行出錯(cuò)與解決方法,結(jié)合具體實(shí)例形式分析了Java Web項(xiàng)目部署在Tomcat過程中由于xml配置文件導(dǎo)致的錯(cuò)誤問題常見提示與解決方法,需要的朋友可以參考下2017-03-03Spring將MultipartFile轉(zhuǎn)存到本地磁盤的三種方式
在Java中處理文件向來是一種不是很方便的操作,然后隨著Spring框架的崛起,使用Spring框架中的MultipartFile來處理文件也是件很方便的事了,今天就給大家介紹Spring將MultipartFile轉(zhuǎn)存到本地磁盤的方式,需要的朋友可以參考下2024-10-10Spring?Boot循環(huán)依賴原理、解決方案與最佳實(shí)踐(全解析)
循環(huán)依賴指兩個(gè)或多個(gè)Bean相互直接或間接引用,形成閉環(huán)依賴關(guān)系,這篇文章主要介紹了Spring?Boot循環(huán)依賴原理、解決方案與最佳實(shí)踐(全解析),需要的朋友可以參考下2025-04-04基于idea操作hbase數(shù)據(jù)庫并映射到hive表
這篇文章主要介紹了用idea操作hbase數(shù)據(jù)庫,并映射到hive,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03教你用springboot連接mysql并實(shí)現(xiàn)增刪改查
今天教各位小伙伴用springboot連接mysql并實(shí)現(xiàn)增刪改查功能,文中有非常詳細(xì)的步驟及代碼示例,對(duì)正在學(xué)習(xí)Java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05