74.謹慎的實現Serializable接口
簡介
一個類只要聲明實現
Serializable
接口,即可被序列化.雖然一個類實現序列化的直接開銷不高,但是長遠影響
卻值得考慮
長期開銷:
- 一旦一個類被髮布,就大大降低了”改變這個類的實現“的靈活性
- 增加了出現
Bug
和安全漏洞的可能性,因爲它和構造器功能類似,比如對單例模式
有影響 - 隨着發行新的版本,相關的測試負擔也增加了.
- 爲繼承而設計的類,應儘可能少的去實現
Serializable
,接口也應儘可能少的繼承Serializable
接口 - 如果一個專爲繼承設計的類沒有實現
Serializable
,那麼就不可能寫出可序列化的子類. - 爲繼承而設計的不可序列化的類,應該提供一個
無參構造器
- 內部類不應該實現
Serializable
,因爲其默認序列化形式是定義不清楚的 - 靜態成員類可以實現
Serializable
75.考慮使用自定義的序列化形式
簡介
- 如果想實現一個
用完即丟棄
的臨時實現.最好不要實現Serializable
接口,因爲這會永遠牽制住這個類
的序列化形式
,比如Java
中的BigInteger
類 - 如果沒有認真考慮默認的序列化形式是否合適,就不要貿然接受
- 如果一個對象的
物理表示法
等同於它的邏輯內容
,就可能適合於使用默認的序列化形式 - 即使確定了默認的序列化形式是合適的,也應該提供一個
readObject
的方法 以約束關係
和保持安全性
如果一個類的
物理表示法
與它的邏輯數據內容
有實質性的區別時,使用默認序列化就會有一下問題
- 使這個類的導出
API
永遠束縛在該類
的內部表示法上 - 它會消耗過多的空間
- 它會引起棧溢出
注意事項
- 在確定將一個域做成非
transient
之前,請一定要確認它的值將是該對象邏輯狀態的一部分 - 如果在
讀取
整個對象狀態
的任何其他方法上強制任何同步,則也必須在對象序列化
上強制這種同步
76.保護性的編寫readObject
簡介
readObject
方法相當於另一個公有的構造器
readObject
可以說是將字節流作爲唯一參數
- 反序列化的時候,readObject如果不進行深拷貝、以及數據合法性驗證,就會導致生成的對象數據非法
- 不要使用
writeUnshared
和readUnshared
方法,因爲它們不安全
() - 非
final
類,構造函數以及readObject方法中,不能調用可重載的方法
建議
爲了編寫出健壯的readObject
方法,有以下方針
- 對象應用域必須保持私有的類,要保護性的拷貝這些域中的每個對象
- 對於任何約束條件,如果檢查失敗,則拋出異常
- 如果整個對象圖在被反序列化後必須驗證,則應該使用
ObjectInputValidation
- 無論是直接形式還是間接形式,都
不要調用
類中任何可被覆蓋的方法.
77.對於實例控制,枚舉類優先於readResolve
簡介
readResolve
特性允許你用readObject
創建的實例代替另一個實例.
如果依賴readResolve
來進行單例控制,則引用類型的所有實例域都應該是transient
(序列化會忽略該字段)的,
- 在1.5之後,
readResolve
就不再是在
可序列化的類中`維持實例控制的最佳方法了 - 最好使用枚舉來實現可序列化的實例控制,有JVM對此提供保障
readResolve
的可訪問性很重要.對於final
和非final
類其訪問性不同
小結
應該儘可能的使用枚舉類型來實施實例控制的約束條件.
否則就必須提供一個readResolve
方法,並確保該類的所有實例域
都爲基本類型
,或者是 transient
的.
78.考慮用序列化代理代替序列化實例
簡介
序列化代理
就是爲可序列化的類 設計一個私有的靜態嵌套類.精確的表示外圍類的實例的邏輯狀態,這個類就是序列化代理類.
源碼實例 :Period.java
示例中,通過內部類SerializationProxy
和writeReplace
及readObject
,readResolve
等方法的運用,
就可以很方便的實現一個不受序列化攻擊
威脅的類.
序列化代理
模式的功能比保護性拷貝
的更加強大,序列化代理
模式允許反序列化實例
有着與原始序列化實例不同的類.
序列化代理模式的兩個侷限
- 不能與可以被客戶端擴展的類兼容,也不能於對象圖中包含循環的某些類兼容.(如果企圖從序列化代理的readResolve方法內部調用對象中的方法,會得到
ClasscastException
,因爲還沒有這兒對象,只有它的序列化代理類) - 比
保護性拷貝
的開銷更大
小結
- 每當發現 要在一個不能被客戶端擴展的類上編寫
readObject
或writeObject
,就應該使用序列化代理模式
- 最好手動指定
serialVersionUID
- 序列化並不保存靜態變量。
- 要想將父類對象也序列化,就需要讓父類也實現
Serializable
接口。如果父類實現的話的,就 需要有默認的無參的構造函數。 - 在變量聲明前加上
transient
,可以阻止該變量被序列化到文件中,在被反序化後,transient
變量的值被設爲初始值,如 int 型的是 0 writeObject
和readObject
方法用於對敏感字段加密- 序列化兩次寫入相同的對象,第二次只會存儲引用關係
- 序列化後存入的對象,修改字段值後繼續存入,兩次讀取都只會讀取到第一個值。
writeReplace
在writeOjbect
方法之前修改序列化的對象。readresolve
在readObject
方法之後控制反序列化時得到的對象