??允許對(duì)類的實(shí)例進(jìn)行序列化可以非常簡(jiǎn)單,只需將單詞implements Serializable添加到其聲明中即可。因?yàn)檫@很容易做到,所以有一個(gè)普遍的誤解,認(rèn)為序列化只需要程序員付出很少的努力。事實(shí)要復(fù)雜得多。雖然使類可序列化的即時(shí)成本可以忽略不計(jì),但長(zhǎng)期成本通常是巨大的。
??實(shí)現(xiàn)Serializable的一個(gè)主要成本是,一旦類的實(shí)現(xiàn)被釋放,它就會(huì)降低更改該類實(shí)現(xiàn)的靈活性。當(dāng)類實(shí)現(xiàn)Serializable時(shí),其字節(jié)流編碼(或序列化形式)成為其導(dǎo)出API的一部分。一旦廣泛分發(fā)了一個(gè)類,通常就需要永遠(yuǎn)支持序列化的表單,就像需要支持導(dǎo)出API的所有其他部分一樣。如果您不努力設(shè)計(jì)自定義序列化表單,而只是接受默認(rèn)值,則序列化表單將永遠(yuǎn)綁定到類的原始內(nèi)部表示。換句話說,如果您接受默認(rèn)的序列化表單,類的私有和包私有實(shí)例字段將成為其導(dǎo)出API的一部分,并且最小化字段訪問的實(shí)踐(item15)將失去其作為信息隱藏工具的有效性。
??如果您接受默認(rèn)的序列化表單,然后更改類的內(nèi)部表示,則會(huì)導(dǎo)致序列化表單中不兼容的更改。試圖使用類的舊版本序列化實(shí)例并使用新版本反序列化實(shí)例的客戶機(jī)(反之亦然)將經(jīng)歷程序失敗。在維護(hù)原始序列化表單的同時(shí),可以更改內(nèi)部表示(使用ObjectOutputStream.putFields和ObjectInputStream.readFields),但是這很困難,并且會(huì)在源代碼中留下明顯的缺陷。如果您選擇使類可序列化,您應(yīng)該仔細(xì)設(shè)計(jì)一個(gè)高質(zhì)量的序列化表單,以便長(zhǎng)期使用(第87、90項(xiàng))。這樣做會(huì)增加開發(fā)的初始成本,但是這樣做是值得的。即使是設(shè)計(jì)良好的序列化形式,也會(huì)限制類的演化;設(shè)計(jì)不良的序列化表單可能會(huì)造成嚴(yán)重后果。
??可串行化性對(duì)演化施加的約束的一個(gè)簡(jiǎn)單示例涉及流惟一標(biāo)識(shí)符(通常稱為串行版本)
uid。每個(gè)可序列化的類都有一個(gè)與之關(guān)聯(lián)的惟一標(biāo)識(shí)號(hào)。如果您沒有通過聲明一個(gè)名為serialVersionUID的靜態(tài)final長(zhǎng)字段來指定這個(gè)數(shù)字,系統(tǒng)在運(yùn)行時(shí)通過對(duì)類的結(jié)構(gòu)應(yīng)用加密哈希函數(shù)(SHA-1)自動(dòng)生成它。這個(gè)值受類的名稱、它實(shí)現(xiàn)的接口及其大多數(shù)成員(包括編譯器生成的合成成員)的影響。如果你改變了這些的任何,比如,通過添加一個(gè)方便的方法,生成的串行版本UID發(fā)生了變化。如果您未能聲明串行版本UID,兼容性將被破壞,從而在運(yùn)行時(shí)導(dǎo)致InvalidClassException異常。
??實(shí)現(xiàn)Serializable的第二個(gè)代價(jià)是增加了bug和安全漏洞的可能性(item85 )。通常,對(duì)象是用構(gòu)造函數(shù)創(chuàng)建的;序列化是一種用于創(chuàng)建對(duì)象的超語言機(jī)制。無論您接受默認(rèn)行為還是覆蓋它,反序列化都是一個(gè)“隱藏構(gòu)造函數(shù)”,與其他構(gòu)造函數(shù)具有相同的所有問題。由于沒有與反序列化關(guān)聯(lián)的顯式構(gòu)造函數(shù),因此很容易忘記必須確保它保證構(gòu)造函數(shù)建立的所有不變量,并且不允許攻擊者訪問正在構(gòu)造的對(duì)象的內(nèi)部。賴默認(rèn)的反序列化機(jī)制可以很容易地讓對(duì)象暴露于不變的破壞和非法訪問(item88 )。
??實(shí)現(xiàn)Serializable的第三個(gè)成本是,它增加了與發(fā)布類的新版本相關(guān)的測(cè)試負(fù)擔(dān)。當(dāng)一個(gè)可序列化的類被修改時(shí),重要的是檢查是否可以在新版本中序列化一個(gè)實(shí)例,并在舊版本中反序列化它,反之亦然。因此,所需的測(cè)試量與可序列化類的數(shù)量和版本的數(shù)量成正比,這兩個(gè)數(shù)量可能很大。您必須確保序列化-反序列化過程成功,并確保它生成原始對(duì)象的忠實(shí)副本。如果在第一次編寫類時(shí)仔細(xì)設(shè)計(jì)了自定義序列化表單,那么測(cè)試的需求就會(huì)減少 (Items 87, 90).
??實(shí)現(xiàn)Serializable并不是一個(gè)輕松的決定。如果一個(gè)類要參與一個(gè)依賴于Java序列化來進(jìn)行對(duì)象傳輸或持久性的框架,這是非常重要的。此外,它還極大地簡(jiǎn)化了將類作為必須實(shí)現(xiàn)Serializable的另一個(gè)類中的組件的使用。然而,與實(shí)現(xiàn)Serializable相關(guān)的成本很多。每次設(shè)計(jì)一個(gè)類時(shí),都要權(quán)衡利弊。歷史上,像BigInteger和Instant這樣的值類實(shí)現(xiàn)了序列化,集合類也實(shí)現(xiàn)了序列化。表示活動(dòng)實(shí)體(如線程池)的類很少應(yīng)該實(shí)現(xiàn)Serializable。
??為繼承而設(shè)計(jì)的類(item19)應(yīng)該很少實(shí)現(xiàn) 可序列化的,接口很少應(yīng)該擴(kuò)展它。違反此規(guī)則會(huì)給擴(kuò)展類或?qū)崿F(xiàn)接口的任何人帶來很大的負(fù)擔(dān)。有時(shí)違反規(guī)則是適當(dāng)?shù)?。例如,如果一個(gè)類或接口的存在主要是為了參與一個(gè)要求所有參與者實(shí)現(xiàn)Serializable的框架,那么類或接口實(shí)現(xiàn)或擴(kuò)展Serializable可能是有意義的。
??為繼承而設(shè)計(jì)的實(shí)現(xiàn)序列化的類包括
Throwable和Component。Throwable實(shí)現(xiàn)了Serializable,因此RMI可以將異常從服務(wù)器發(fā)送到客戶機(jī)。Component實(shí)現(xiàn)了序列化
gui可以被發(fā)送、保存和恢復(fù),但是即使在Swing和AWT的鼎盛時(shí)期,這個(gè)工具在實(shí)踐中也很少使用。
??如果您使用實(shí)例字段實(shí)現(xiàn)了一個(gè)既可序列化又可擴(kuò)展的類,那么需要注意幾個(gè)風(fēng)險(xiǎn)。如果實(shí)例字段值上有任何不變量,關(guān)鍵是要防止子類覆蓋finalize方法,該類可以通過覆蓋finalize并聲明它為final來實(shí)現(xiàn)這一點(diǎn)。否則,該類將容易受到終結(jié)器攻擊(item8)。最后,如果類的實(shí)例字段初始化為默認(rèn)值(整數(shù)類型為0,布爾值為false,對(duì)象引用類型為null),那么必須添加readObjectNoData方法:

??這個(gè)方法是在Java 4中添加的,以涵蓋一個(gè)涉及到將可序列化超類添加到現(xiàn)有可序列化類[serializable, 3.5]的特殊情況。
??關(guān)于不實(shí)現(xiàn)Serializable的決定,有一個(gè)警告。如果為繼承而設(shè)計(jì)的類不可序列化,則可能需要額外的工作來編寫可序列化的子類。此類的常規(guī)反序列化要求超類具有可訪問的無參數(shù)構(gòu)造函數(shù)[Serialization, 1.10]。如果不提供這樣的構(gòu)造函數(shù),子類將被迫使用序列化代理模式( item90 )。
??內(nèi)部類(內(nèi)部類(第24項(xiàng))不應(yīng)該實(shí)現(xiàn)Serializable。)不應(yīng)該實(shí)現(xiàn)Serializable。它們使用編譯器生成的合成字段存儲(chǔ)對(duì)封閉實(shí)例的引用,并存儲(chǔ)來自封閉范圍的局部變量的值。這些字段與類定義的對(duì)應(yīng)關(guān)系,以及匿名類和本地類的名稱都是未指定的。因此,內(nèi)部類的默認(rèn)序列化形式定義不正確。但是,靜態(tài)成員類可以實(shí)現(xiàn)Serializable。
??總而言之,實(shí)現(xiàn)Serializable的簡(jiǎn)單性是似是而非的。除非類只在受保護(hù)的環(huán)境中使用,在這種環(huán)境中,版本永遠(yuǎn)不必互操作,服務(wù)器永遠(yuǎn)不會(huì)暴露給不可信的數(shù)據(jù),否則實(shí)現(xiàn)
Serializable是一個(gè)嚴(yán)肅的承諾,應(yīng)該非常謹(jǐn)慎地做出。如果類允許繼承,則需要格外小心。
本文寫于2019.7.24,歷時(shí)1天