生活随笔
收集整理的這篇文章主要介紹了
java 75-76
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
?七十五、考慮使用自定義的序列化形式:?
????? 設(shè)計一個類的序列化形式和設(shè)計該類的API同樣重要,因此在沒有認真考慮好默認的序列化形式是否合適之前,不要貿(mào)然使用默認的序列化行為。在作出決定之前,你需要從靈活性、性能和正確性多個角度對這種編碼形式進行考察。一般來講,只有當你自行設(shè)計的自定義序列化形式與默認的形式基本相同時,才能接受默認的序列化形式。比如,當一個對象的物理表示法等同于它的邏輯內(nèi)容,可能就適合于使用默認的序列化形式。見如下代碼示例:
public?class?Name?implements?Serializable?{??????????private?final?String?lastName;??????????private?final?String?firstName;??????????private?final?String?middleName;??????????...?...??????}?
從邏輯角度而言,該類的三個域字段精確的反應(yīng)出它的邏輯內(nèi)容。然而有的時候,即便默認的序列化形式是合適的,通常還必須提供一個readObject方法以保證約束關(guān)系和安全性,如上例代碼中,firstName和lastName不能為null等。
????? 下面我們再看一個極端的例子:
public?final?class?StringList?implements?Serializable?{??????????private?int?size?=?0;??????????private?Entry?head?=?null;??????????private?static?class?Entry?implements?Serializable?{??????????????String?data;??????????????Entry?next;??????????????Entry?previous;??????????}??????}?
?對于上面的示例代碼,如果采用默認形式的序列化,將會導(dǎo)致雙向鏈表中的每一個節(jié)點的數(shù)據(jù)以及前后關(guān)系都會被序列化。因此這種物理表示法與它的邏輯數(shù)據(jù)內(nèi)容有實質(zhì)性的區(qū)別時,使用默認序列化形式會有以下幾個缺點:
????? 1. 它使這個類的導(dǎo)出API永遠的束縛在該類的內(nèi)部表示法上,即使今后找到更好的的實現(xiàn)方式,也無法擺脫原有的實現(xiàn)方式。
????? 2. 它會消耗過多的空間。事實上對于上面的示例代碼,我們只需要序列化數(shù)據(jù)部分,可以完全忽略鏈表節(jié)點之間的關(guān)系。
????? 3. 它會消耗過多的時間。
????? 4. 它會引起棧溢出。
???? 根據(jù)以上四點,我們修訂了StringList類的序列化實現(xiàn)方式,見如下代碼:
public?final?class?StringList?implements?Serializable?{??????????private?transient?int?size?=?0;??????????private?transient?Entry?head?=?null;??????????private?static?class?Entry?{??????????????String?data;??????????????Entry?next;??????????????Entry?previous;??????????}??????????private?void?writeObject(ObjectOutputStream?s)?throws?IOException?{??????????????s.defaultWriteObject();??????????????s.writeInt(size);??????????????for?(Entry?e?=?head;?e?!=?null;?e?=?e.next)??????????????????s.writeObject(e.data);??????????}??????????private?void?readObject(ObjectInputStream?s)???????????????throws?IOException,?ClassNotFoundException?{??????????????s.defaultReadObject();??????????????int?numElemnet?=?s.readInt();??????????????for?(int?i?=?0;?i?<?numElements;?i++)??????????????????add((String)s.readObject());??????????}??????????public?final?void?add(String?s)?{?...?}??????????...?...??????}?
在修訂代碼中,所有的域字段都是transient,但writeObject和readObject方法的首要任務(wù)仍然是先調(diào)用defaultWriteObject和defaultReadObject方法,即便這對于缺省序列化形式并不是必須的。因為在今后的修改中,很有可能會為該類添加非transient域字段,一旦忘記同步修改writeObject或readObject方法,將會導(dǎo)致序列化和反序列化的數(shù)據(jù)處理方式不一致。
????? 對于默認序列化還需要進一步說明的是,當一個或多個域字段被標記為transient時,如果要進行反序列化,這些域字段都將被初始化為其類型默認值,如對象引用域被置為null,數(shù)值基本域的默認值為0,boolean域的默認值為false。如果這些值不能被任何transient域所接受,你就必須提供一個readObject方法。它首先調(diào)用defaultReadObject,然后再把這些transient域恢復(fù)為可接受的值。
????? 最后需要說明的是,無論你是否使用默認的序列化形式,如果在讀取整個對象狀態(tài)的任何其他方法上強制任何同步,則也必須在對象序列化上強制這種同步,見如下代碼:
private?synchronized?void?writeObject(ObjectOutputStream?s)?throws?IOException?{??????????s.defaultWriteObject();??????}?
七十六、保護性的編寫readObject方法:
????? 在條目39中介紹了一個不可變的日期范圍類,它包含可變的私有Date域。該類通過在其構(gòu)造器和訪問方法中保護性的拷貝Date對象,極力的維護其約束條件和不可變性。見如下代碼:
public?final?class?Period?{??????????private?final?Date?start;??????????private?final?Date?end;??????????public?Period(Date?start,?Date?end)?{??????????????this.start?=?new?Date(start.getTime());??????????????this.end?=?new?Date(end.getTime());??????????????if?(this.start.compareTo(this.end)?>?0)??????????????????throw?new?IllegalArgumentException();??????????}??????????public?Date?start()?{??????????????return?new?Date(start.getTime());??????????}??????????public?Date?end()?{??????????????return?new?Date(end.getTime());??????????}??????????public?String?toString()?{??????????????return?start?+?"?-?"?+?end;??????????}??????????...?...??????}?
?這個對象的物理表示法和其邏輯表示法完全匹配,所以我們可以使用默認的序列化形式。因此在聲明該類的地方增加" implements Serializable "。然而,如果你真是這樣做了,那么這個類將不再保證他的關(guān)鍵約束了。
????? 問題在于,如果反序列化的數(shù)據(jù)源來自于該類實例的正常序列化,那么將不會引發(fā)任何問題。如果恰恰相反,反序列化的數(shù)據(jù)源來自于一組偽造的數(shù)據(jù)流,事實上,反序列化的機制就是從一組有規(guī)則的數(shù)據(jù)流中實例化指定對象,那么我們將不得不面對Period實例對象的內(nèi)部約束被破壞的危險,見如下代碼:
public?class?BogusPeriod?{??????????private?static?final?byte[]?serializedForm?=?new?byte[]?{??????????????...?...???????????};??????????public?static?void?main(String[]?args)?[??????????????Period?p?=?(Period)deserialize(serializedForm);??????????????System.out.println(p);??????????}??????????private?static?Object?deserialize(byte[]?sf)?{??????????????try?{??????????????????InputStream?is?=?new?ByteArrayInputStream(sf);??????????????????ObjectInputStream?ois?=?new?ObjectInputStream(is);??????????????????return?ois.readObject();??????????????}?catch?(Exception?e)?{??????????????????throw?new?IllegalArgumentException(e);??????????????}??????????}??????}? ?如果執(zhí)行上面的代碼就會發(fā)現(xiàn)Period的約束被打破了,end的日期早于start。為了修正這個問題,可以為Period提供一個readObject方法,該方法首先調(diào)用defaultReadObject,然后檢查被反序列化之后的對象的有效性。如果檢查失敗,則拋出InvalidObjectException異常,使反序列化過程不能成功地完成。
private?void?readObject(ObjectInputStream?s)??????????throws?IOException,ClassNotFoundException?{??????????s.defaultReadObject();??????????if?(start.compareTo(end)?>?0)??????????????throw?new?InvalidObjectException(start?+?"?after?"?+?end);??????}?
如果執(zhí)行上面的代碼就會發(fā)現(xiàn)Period的約束被打破了,end的日期早于start。為了修正這個問題,可以為Period提供一個readObject方法,該方法首先調(diào)用defaultReadObject,然后檢查被反序列化之后的對象的有效性。如果檢查失敗,則拋出InvalidObjectException異常,使反序列化過程不能成功地完成。
private?void?readObject(ObjectInputStream?s)??????????throws?IOException,ClassNotFoundException?{??????????s.defaultReadObject();??????????if?(start.compareTo(end)?>?0)??????????????throw?new?InvalidObjectException(start?+?"?after?"?+?end);??????}? 除了上面的***方式之外,還存在著另外一種更為隱匿的***方式,它也是通過偽造序列化數(shù)據(jù)流的方式來騙取反序列化方法的信任。它在偽造數(shù)據(jù)時,將私有域字段的引用在外部保存起來,這樣當對象實例反序列化成功后,由于外部仍然可以操作其內(nèi)部數(shù)據(jù),因此危險仍然存在。如何避免該風(fēng)險呢?見如下修訂后的readObject方法:
private?void?readObject(ObjectInputStream?s)??????????throws?IOException,ClassNotFoundException?{??????????s.defaultReadObject();????????????????????start?=?new?Date(start.getTime());??????????end?=?new?Date(end.getTime());??????????if?(start.compareTo(end)?>?0)??????????????throw?new?InvalidObjectException(start?+?"?after?"?+?end);??????}?
注意,保護性copy一定要在有效性檢查之前進行。
????? 這里給出一個基本的規(guī)則,可以用來幫助確定默認的readObject方法是否可以被接受。規(guī)則是增加一個公有的構(gòu)造器,其參數(shù)對應(yīng)于該對象中每個非transient域,并且無論參數(shù)的值是什么,都是不進行檢查就可以保存到相應(yīng)的域中的。對于這樣的做法如果仍然可以接受,那么默認的readObject就是合理,否則就需要提供一個顯式的readObject方法。
????? 對于非final的可序列化類,在readObject方法和構(gòu)造器之間還有其他類似的地方,readObject方法不可以調(diào)用可被覆蓋的方法,無論是直接調(diào)用還是間接調(diào)都不可以。如果違反了該規(guī)則,并且覆蓋了該方法,被覆蓋的方法將在子類的狀態(tài)被反序列化之前先運行。程序很可能會失敗。
轉(zhuǎn)載于:https://blog.51cto.com/andra/780485
總結(jié)
以上是生活随笔為你收集整理的java 75-76的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。