Serializable:明明就一个空接口!为什么还要实现它?
作者:沉默王二
微信公眾號(hào):Java極客技術(shù)(ID:Javageektech)
對(duì)于 Java 的序列化,我一直停留在最淺顯的認(rèn)知上——把那個(gè)要序列化的類實(shí)現(xiàn)?Serializbale?接口就可以了。我不愿意做更深入的研究,因?yàn)闀?huì)用就行了嘛。
但隨著時(shí)間的推移,見到?Serializbale?的次數(shù)越來(lái)越多,我便對(duì)它產(chǎn)生了濃厚的興趣。是時(shí)候花點(diǎn)時(shí)間研究研究了。
01、先來(lái)點(diǎn)理論
Java 序列化是 JDK 1.1 時(shí)引入的一組開創(chuàng)性的特性,用于將 Java 對(duì)象轉(zhuǎn)換為字節(jié)數(shù)組,便于存儲(chǔ)或傳輸。此后,仍然可以將字節(jié)數(shù)組轉(zhuǎn)換回 Java 對(duì)象原有的狀態(tài)。
序列化的思想是“凍結(jié)”對(duì)象狀態(tài),然后寫到磁盤或者在網(wǎng)絡(luò)中傳輸;反序列化的思想是“解凍”對(duì)象狀態(tài),重新獲得可用的 Java 對(duì)象。
再來(lái)看看序列化?Serializbale?接口的定義:
public?interface?Serializable?{ }明明就一個(gè)空的接口嘛,竟然能夠保證實(shí)現(xiàn)了它的“類的對(duì)象”被序列化和反序列化?
02、再來(lái)點(diǎn)實(shí)戰(zhàn)
在回答上述問題之前,我們先來(lái)創(chuàng)建一個(gè)類(只有兩個(gè)字段,和對(duì)應(yīng)的?getter/setter),用于序列化和反序列化。
class?Wanger?{private?String?name;private?int?age;public?String?getName()?{return?name;}public?void?setName(String?name)?{this.name?=?name;}public?int?getAge()?{return?age;}public?void?setAge(int?age)?{this.age?=?age;} }再來(lái)創(chuàng)建一個(gè)測(cè)試類,通過(guò)?ObjectOutputStream?將“18 歲的王二”寫入到文件當(dāng)中,實(shí)際上就是一種序列化的過(guò)程;再通過(guò)?ObjectInputStream?將“18 歲的王二”從文件中讀出來(lái),實(shí)際上就是一種反序列化的過(guò)程。
public?class?Test?{public?static?void?main(String[]?args)?{//?初始化Wanger?wanger?=?new?Wanger();wanger.setName("王二");wanger.setAge(18);System.out.println(wanger);//?把對(duì)象寫到文件中try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("chenmo"));){oos.writeObject(wanger);}?catch?(IOException?e)?{e.printStackTrace();}//?從文件中讀出對(duì)象try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(new?File("chenmo")));){Wanger?wanger1?=?(Wanger)?ois.readObject();System.out.println(wanger1);}?catch?(IOException?|?ClassNotFoundException?e)?{e.printStackTrace();}}}不過(guò),由于?Wanger?沒有實(shí)現(xiàn)?Serializbale?接口,所以在運(yùn)行測(cè)試類的時(shí)候會(huì)拋出異常,堆棧信息如下:
java.io.NotSerializableException:?com.cmower.java_demo.xuliehua.Wangerat?java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at?java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at?com.cmower.java_demo.xuliehua.Test.main(Test.java:21)順著堆棧信息,我們來(lái)看一下?ObjectOutputStream?的?writeObject0()?方法。其部分源碼如下:
if?(obj?instanceof?String)?{writeString((String)?obj,?unshared); }?else?if?(cl.isArray())?{writeArray(obj,?desc,?unshared); }?else?if?(obj?instanceof?Enum)?{writeEnum((Enum<?>)?obj,?desc,?unshared); }?else?if?(obj?instanceof?Serializable)?{writeOrdinaryObject(obj,?desc,?unshared); }?else?{if?(extendedDebugInfo)?{throw?new?NotSerializableException(cl.getName()?+?"\n"?+?debugInfoStack.toString());}?else?{throw?new?NotSerializableException(cl.getName());} }也就是說(shuō),ObjectOutputStream?在序列化的時(shí)候,會(huì)判斷被序列化的對(duì)象是哪一種類型,字符串?數(shù)組?枚舉?還是?Serializable,如果全都不是的話,拋出?NotSerializableException。
假如?Wanger?實(shí)現(xiàn)了?Serializable?接口,就可以序列化和反序列化了。
class?Wanger?implements?Serializable{private?static?final?long?serialVersionUID?=?-2095916884810199532L;private?String?name;private?int?age; }具體怎么序列化呢?
以?ObjectOutputStream?為例吧,它在序列化的時(shí)候會(huì)依次調(diào)用?writeObject()→writeObject0()→writeOrdinaryObject()→writeSerialData()→invokeWriteObject()→defaultWriteFields()。
private?void?defaultWriteFields(Object?obj,?ObjectStreamClass?desc)throws?IOException{Class<?>?cl?=?desc.forClass();desc.checkDefaultSerialize();int?primDataSize?=?desc.getPrimDataSize();desc.getPrimFieldValues(obj,?primVals);bout.write(primVals,?0,?primDataSize,?false);ObjectStreamField[]?fields?=?desc.getFields(false);Object[]?objVals?=?new?Object[desc.getNumObjFields()];int?numPrimFields?=?fields.length?-?objVals.length;desc.getObjFieldValues(obj,?objVals);for?(int?i?=?0;?i?<?objVals.length;?i++)?{try?{writeObject0(objVals[i],fields[numPrimFields?+?i].isUnshared());}}}那怎么反序列化呢?
以?ObjectInputStream?為例,它在反序列化的時(shí)候會(huì)依次調(diào)用?readObject()→readObject0()→readOrdinaryObject()→readSerialData()→defaultReadFields()。
private?void?defaultWriteFields(Object?obj,?ObjectStreamClass?desc)throws?IOException{Class<?>?cl?=?desc.forClass();desc.checkDefaultSerialize();int?primDataSize?=?desc.getPrimDataSize();desc.getPrimFieldValues(obj,?primVals);bout.write(primVals,?0,?primDataSize,?false);ObjectStreamField[]?fields?=?desc.getFields(false);Object[]?objVals?=?new?Object[desc.getNumObjFields()];int?numPrimFields?=?fields.length?-?objVals.length;desc.getObjFieldValues(obj,?objVals);for?(int?i?=?0;?i?<?objVals.length;?i++)?{try?{writeObject0(objVals[i],fields[numPrimFields?+?i].isUnshared());}}}我想看到這,你應(yīng)該會(huì)恍然大悟的“哦”一聲了。Serializable?接口之所以定義為空,是因?yàn)樗黄鸬搅艘粋€(gè)標(biāo)識(shí)的作用,告訴程序?qū)崿F(xiàn)了它的對(duì)象是可以被序列化的,但真正序列化和反序列化的操作并不需要它來(lái)完成。
03、再來(lái)點(diǎn)注意事項(xiàng)
開門見山的說(shuō)吧,static?和?transient?修飾的字段是不會(huì)被序列化的。
為什么呢?我們先來(lái)證明,再來(lái)解釋原因。
首先,在?Wanger?類中增加兩個(gè)字段。
class?Wanger?implements?Serializable?{private?static?final?long?serialVersionUID?=?-2095916884810199532L;private?String?name;private?int?age;public?static?String?pre?=?"沉默";transient?String?meizi?=?"王三";@Overridepublic?String?toString()?{return?"Wanger{"?+?"name="?+?name?+?",age="?+?age?+?",pre="?+?pre?+?",meizi="?+?meizi?+?"}";} }其次,在測(cè)試類中打印序列化前和反序列化后的對(duì)象,并在序列化后和反序列化前改變?static?字段的值。具體代碼如下:
//?初始化 Wanger?wanger?=?new?Wanger(); wanger.setName("王二"); wanger.setAge(18); System.out.println(wanger);//?把對(duì)象寫到文件中 try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("chenmo"));){oos.writeObject(wanger);}?catch?(IOException?e)?{e.printStackTrace();}//?改變?static?字段的值 Wanger.pre?="不沉默";//?從文件中讀出對(duì)象 try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(new?File("chenmo")));){Wanger?wanger1?=?(Wanger)?ois.readObject();System.out.println(wanger1); }?catch?(IOException?|?ClassNotFoundException?e)?{e.printStackTrace(); } //?Wanger{name=王二,age=18,pre=沉默,meizi=王三} //?Wanger{name=王二,age=18,pre=不沉默,meizi=null}從結(jié)果的對(duì)比當(dāng)中,我們可以發(fā)現(xiàn):
1)序列化前,pre?的值為“沉默”,序列化后,pre?的值修改為“不沉默”,反序列化后,pre?的值為“不沉默”,而不是序列化前的狀態(tài)“沉默”。
為什么呢?因?yàn)樾蛄谢4娴氖菍?duì)象的狀態(tài),而?static?修飾的字段屬于類的狀態(tài),因此可以證明序列化并不保存?static?修飾的字段。
2)序列化前,meizi?的值為“王三”,反序列化后,meizi?的值為?null,而不是序列化前的狀態(tài)“王三”。
為什么呢?transient?的中文字義為“臨時(shí)的”(論英語(yǔ)的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient?字段的值被設(shè)為初始值,比如?int型的初始值為 0,對(duì)象型的初始值為?null。
如果想要深究源碼的話,你可以在?ObjectStreamClass?中發(fā)現(xiàn)下面這樣的代碼:
private?static?ObjectStreamField[]?getDefaultSerialFields(Class<?>?cl)?{Field[]?clFields?=?cl.getDeclaredFields();ArrayList<ObjectStreamField>?list?=?new?ArrayList<>();int?mask?=?Modifier.STATIC?|?Modifier.TRANSIENT;int?size?=?list.size();return?(size?==?0)???NO_FIELDS?:list.toArray(new?ObjectStreamField[size]); }看到?Modifier.STATIC | Modifier.TRANSIENT,是不是感覺更好了呢?
04、再來(lái)點(diǎn)干貨
除了?Serializable?之外,Java 還提供了一個(gè)序列化接口?Externalizable(念起來(lái)有點(diǎn)拗口)。
兩個(gè)接口有什么不一樣的嗎?試一試就知道了。
首先,把?Wanger?類實(shí)現(xiàn)的接口 ?Serializable?替換為?Externalizable。
class?Wanger?implements?Externalizable?{private?String?name;private?int?age;public?Wanger()?{}public?String?getName()?{return?name;}@Overridepublic?String?toString()?{return?"Wanger{"?+?"name="?+?name?+?",age="?+?age?+?"}";}@Overridepublic?void?writeExternal(ObjectOutput?out)?throws?IOException?{}@Overridepublic?void?readExternal(ObjectInput?in)?throws?IOException,?ClassNotFoundException?{}}實(shí)現(xiàn)?Externalizable?接口的?Wanger?類和實(shí)現(xiàn)?Serializable?接口的?Wanger?類有一些不同:
1)新增了一個(gè)無(wú)參的構(gòu)造方法。
使用?Externalizable?進(jìn)行反序列化的時(shí)候,會(huì)調(diào)用被序列化類的無(wú)參構(gòu)造方法去創(chuàng)建一個(gè)新的對(duì)象,然后再將被保存對(duì)象的字段值復(fù)制過(guò)去。否則的話,會(huì)拋出以下異常:
java.io.InvalidClassException:?com.cmower.java_demo.xuliehua1.Wanger;?no?valid?constructorat?java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)at?java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)at?java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)at?java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)at?java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)at?com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)2)新增了兩個(gè)方法?writeExternal()?和?readExternal(),實(shí)現(xiàn)?Externalizable?接口所必須的。
然后,我們?cè)僭跍y(cè)試類中打印序列化前和反序列化后的對(duì)象。
//?初始化 Wanger?wanger?=?new?Wanger(); wanger.setName("王二"); wanger.setAge(18); System.out.println(wanger);//?把對(duì)象寫到文件中 try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("chenmo"));)?{oos.writeObject(wanger); }?catch?(IOException?e)?{e.printStackTrace(); }//?從文件中讀出對(duì)象 try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(new?File("chenmo")));)?{Wanger?wanger1?=?(Wanger)?ois.readObject();System.out.println(wanger1); }?catch?(IOException?|?ClassNotFoundException?e)?{e.printStackTrace(); } //?Wanger{name=王二,age=18} //?Wanger{name=null,age=0}從輸出的結(jié)果看,反序列化后得到的對(duì)象字段都變成了默認(rèn)值,也就是說(shuō),序列化之前的對(duì)象狀態(tài)沒有被“凍結(jié)”下來(lái)。
為什么呢?因?yàn)槲覀儧]有為?Wanger?類重寫具體的?writeExternal()?和?readExternal()?方法。那該怎么重寫呢?
@Override public?void?writeExternal(ObjectOutput?out)?throws?IOException?{out.writeObject(name);out.writeInt(age); }@Override public?void?readExternal(ObjectInput?in)?throws?IOException,?ClassNotFoundException?{name?=?(String)?in.readObject();age?=?in.readInt(); }1)調(diào)用?ObjectOutput?的?writeObject()?方法將字符串類型的?name?寫入到輸出流中;
2)調(diào)用?ObjectOutput?的?writeInt()?方法將整型的?age?寫入到輸出流中;
3)調(diào)用?ObjectInput?的?readObject()?方法將字符串類型的?name?讀入到輸入流中;
4)調(diào)用?ObjectInput?的?readInt()?方法將字符串類型的?age?讀入到輸入流中;
再運(yùn)行一次測(cè)試了類,你會(huì)發(fā)現(xiàn)對(duì)象可以正常地序列化和反序列化了。
序列化前:Wanger{name=王二,age=18}
序列化后:Wanger{name=王二,age=18}
05、再來(lái)點(diǎn)甜點(diǎn)
讓我先問問你吧,你知道?private static final long serialVersionUID = -2095916884810199532L;?這段代碼的作用嗎?
嗯……
serialVersionUID?被稱為序列化 ID,它是決定 Java 對(duì)象能否反序列化成功的重要因子。在反序列化時(shí),Java 虛擬機(jī)會(huì)把字節(jié)流中的?serialVersionUID?與被序列化類中的?serialVersionUID?進(jìn)行比較,如果相同則可以進(jìn)行反序列化,否則就會(huì)拋出序列化版本不一致的異常。
當(dāng)一個(gè)類實(shí)現(xiàn)了?Serializable?接口后,IDE 就會(huì)提醒該類最好產(chǎn)生一個(gè)序列化 ID,就像下面這樣:
1)添加一個(gè)默認(rèn)版本的序列化 ID:
private?static?final?long?serialVersionUID?=?1L。2)添加一個(gè)隨機(jī)生成的不重復(fù)的序列化 ID。
private?static?final?long?serialVersionUID?=?-2095916884810199532L;3)添加?@SuppressWarnings?注解。
@SuppressWarnings("serial")怎么選擇呢?
首先,我們采用第二種辦法,在被序列化類中添加一個(gè)隨機(jī)生成的序列化 ID。
class?Wanger?implements?Serializable?{private?static?final?long?serialVersionUID?=?-2095916884810199532L;private?String?name;private?int?age;//?其他代碼忽略 }然后,序列化一個(gè)?Wanger?對(duì)象到文件中。
//?初始化 Wanger?wanger?=?new?Wanger(); wanger.setName("王二"); wanger.setAge(18); System.out.println(wanger);//?把對(duì)象寫到文件中 try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("chenmo"));)?{oos.writeObject(wanger); }?catch?(IOException?e)?{e.printStackTrace(); }這時(shí)候,我們悄悄地把?Wanger?類的序列化 ID 偷梁換柱一下,嘿嘿。
//?private?static?final?long?serialVersionUID?=?-2095916884810199532L; private?static?final?long?serialVersionUID?=?-2095916884810199533L;好了,準(zhǔn)備反序列化吧。
try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(new?File("chenmo")));)?{Wanger?wanger?=?(Wanger)?ois.readObject();System.out.println(wanger); }?catch?(IOException?|?ClassNotFoundException?e)?{e.printStackTrace(); }哎呀,出錯(cuò)了。
java.io.InvalidClassException:??local?class?incompatible:?stream?classdesc? serialVersionUID?=?-2095916884810199532, local?class?serialVersionUID?=?-2095916884810199533at?java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)at?com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)異常堆棧信息里面告訴我們,從持久化文件里面讀取到的序列化 ID 和本地的序列化 ID 不一致,無(wú)法反序列化。
那假如我們采用第三種方法,為?Wanger?類添加個(gè)?@SuppressWarnings("serial")?注解呢?
@SuppressWarnings("serial") class?Wanger3?implements?Serializable?{ //?省略其他代碼 }好了,再來(lái)一次反序列化吧。可惜依然報(bào)錯(cuò)。
java.io.InvalidClassException:??local?class?incompatible:?stream?classdesc? serialVersionUID?=?-2095916884810199532,? local?class?serialVersionUID?=?-3818877437117647968at?java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)at?com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)異常堆棧信息里面告訴我們,本地的序列化 ID 為 -3818877437117647968,和持久化文件里面讀取到的序列化 ID 仍然不一致,無(wú)法反序列化。這說(shuō)明什么呢?使用?@SuppressWarnings("serial")?注解時(shí),該注解會(huì)為被序列化類自動(dòng)生成一個(gè)隨機(jī)的序列化 ID。
由此可以證明,Java 虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,還有一個(gè)非常重要的因素就是序列化 ID 是否一致。
也就是說(shuō),如果沒有特殊需求,采用默認(rèn)的序列化 ID(1L)就可以,這樣可以確保代碼一致時(shí)反序列化成功。
class?Wanger?implements?Serializable?{private?static?final?long?serialVersionUID?=?1L; //?省略其他代碼 }06、再來(lái)點(diǎn)總結(jié)
寫這篇文章之前,我真沒想到:“空空其身”的Serializable?竟然有這么多可以研究的內(nèi)容!
寫完這篇文章之后,我不由得想起理科狀元曹林菁說(shuō)說(shuō)過(guò)的一句話:“在學(xué)習(xí)中再小的問題也不放過(guò),每個(gè)知識(shí)點(diǎn)都要總結(jié)”——說(shuō)得真真真真的對(duì)啊!
總結(jié)
以上是生活随笔為你收集整理的Serializable:明明就一个空接口!为什么还要实现它?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌和 Facebook 是如何给工程师
- 下一篇: e.printStackTrace()不