什么是Java序列化留搔?
持久化內(nèi)存中的對象到硬件設(shè)備贴浙,會把其狀態(tài)保存為一組字節(jié)砂吞,在未來,再將這些字節(jié)組裝成對象崎溃,這就是序列化和反序列化蜻直。
必須注意地是,對象序列化保存的是對象的"狀態(tài)"袁串,即它的成員變量概而。由此可知,對象序列化不會關(guān)注類中的靜態(tài)變量以及被transient關(guān)鍵字修飾的成員變量般婆。
Java序列化的應(yīng)用場景
- 把對象持久化到存儲設(shè)備上
- 對象通過網(wǎng)絡(luò)傳輸給其它客戶端
基本知識點
Serializable概述
- 對于任何需要被序列化的對象,都必須要實現(xiàn)接口Serializable,它只是一個標識接口朵逝,本身沒有任何成員蔚袍,只是用來標識說明當前的實現(xiàn)類的對象可以被序列化.
- 如果父類實現(xiàn)序列化,子類自動實現(xiàn)序列化配名,不需要顯式實現(xiàn)Serializable接口啤咽。
transient關(guān)鍵字
如果某實例變量不能或不應(yīng)該被序列化,就把它標記為transient的變量渠脉,這樣序列化程序就會把它跳過宇整。
transient的引用變量會以null返回,基本數(shù)據(jù)類型會以相應(yīng)的默認值返回芋膘。
(例如:引用類型沒有實現(xiàn)Serializable鳞青,或者動態(tài)數(shù)據(jù)只可以在執(zhí)行時求出而不能或不必存儲)
serialVersionUID
作用:
用來輔助序列化和反序列化過程的,原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當前類的serialVersionUID相同才能夠正常地被反序列化为朋。
工作機制:
序列化的時候系統(tǒng)會把當前類的serialVersionUID寫入序列化的文件中臂拓,當反序列化的時候系統(tǒng)會去檢測文件中的serialVersionUID,看它是否和當前類的serialVersionUID一致习寸,如果一致就說明序列化的類的版本和當前類的版本是相同的胶惰,這個時候可以成功反序列化;否則就說明當前類和序列化的類相比發(fā)生了某些變換霞溪,比如成員變量的數(shù)量孵滞,類型可能發(fā)生了改變中捆,這個時候無法正常反序列化的。并會產(chǎn)生以下異常:
java.io.InvalidClassException: 實體類(pojo); local class incompatible: stream classdesc serialVersionUID = 812952289507407815, local class serialVersionUID = -7688346538714640295
兩種指定serialVersionUID的方式:
- 手動指定serialVersionUID的值坊饶,比如100L
- 通過IDE根據(jù)當前類的結(jié)構(gòu)自動去生成它的hash值泄伪。
指定與不指定serialVersionUID有什么不同的結(jié)果?
- 如果不手動指定serialVersionUID的值幼东,反序列化時當前類有所改變臂容,比如增加或者刪除了某些成員變量,那么系統(tǒng)就會重新計算當前類的hash值并把它賦值給serialVersionUID根蟹,這個時候當前類的serialVersionUID就和序列化的數(shù)據(jù)中的serialVersionUID不一致脓杉,于是反序列化失敗,程序就會拋出java.io.InvalidClassException简逮。
- 如果手動指定了它以后球散,就可以在很大的程度上避免反序列化過程的失敗。比如當版本升級后散庶,我們可能刪除了某個成員變量也可能增加了一些新的成員變量蕉堰,這個時候我們的反序列化過程仍然能成功,程序仍然能夠最大限度地恢復(fù)數(shù)據(jù)悲龟,相反屋讶,如果不指定serialVersionUID的話,程序則會crash须教。
- 如果類結(jié)構(gòu)發(fā)生了非常規(guī)性改變皿渗,比如修改了類名,修改了成員變量的類型轻腺,這個時候盡管serialVersionUID驗證通過乐疆,但是反序列化過程還是會失敗,因為類的結(jié)構(gòu)有了毀滅性的改變贬养,根本無法從老版本的數(shù)據(jù)中還原出一個新的類結(jié)構(gòu)的對象挤土。
簡單實例
File file = new File("box.out");
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(new Box(100, 100));
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Box o = (Box) objectInputStream.readObject();
objectInputStream.close();
System.out.println(o);
注意事項
- 如果有不能被序列化的對象,執(zhí)行期間就會拋出NotSerializableException異常;
- 序列化時误算,只對對象的狀態(tài)進行保存仰美,而不管對象的方法
- 靜態(tài)變量和transient修飾的變量不會被序列化
- 如果子類實現(xiàn)Serializable接口而父類未實現(xiàn)時,父類不會被序列化儿礼,但此時父類必須有個無參構(gòu)造方法筒占,否則會拋InvalidClassException異常。因為反序列化時會恢復(fù)原有子對象的狀態(tài)蜘犁,而父類的成員變量也是原有子對象的一部分翰苫。由于父類沒有實現(xiàn)序列化接口,即使沒有顯示調(diào)用,也會默認執(zhí)行父類的無參構(gòu)造函數(shù)使變量初始化奏窑。