Effective Java : 序列化

74.謹慎的實現Serializable接口

簡介

一個類只要聲明實現Serializable接口,即可被序列化.雖然一個類實現序列化的直接開銷不高,但是長遠影響卻值得考慮

長期開銷:

  1. 一旦一個類被髮布,就大大降低了”改變這個類的實現“的靈活性
  2. 增加了出現Bug和安全漏洞的可能性,因爲它和構造器功能類似,比如對單例模式有影響
  3. 隨着發行新的版本,相關的測試負擔也增加了.
  4. 爲繼承而設計的類,應儘可能少的去實現Serializable,接口也應儘可能少的繼承Serializable接口
  5. 如果一個專爲繼承設計的類沒有實現Serializable,那麼就不可能寫出可序列化的子類.
  6. 爲繼承而設計的不可序列化的類,應該提供一個無參構造器
  7. 內部類不應該實現 Serializable,因爲其默認序列化形式是定義不清楚的
  8. 靜態成員類可以實現Serializable

75.考慮使用自定義的序列化形式

簡介

  • 如果想實現一個用完即丟棄的臨時實現.最好不要實現Serializable接口,因爲這會永遠牽制住這個類序列化形式,比如Java中的BigInteger
  • 如果沒有認真考慮默認的序列化形式是否合適,就不要貿然接受
  • 如果一個對象的物理表示法等同於它的邏輯內容,就可能適合於使用默認的序列化形式
  • 即使確定了默認的序列化形式是合適的,也應該提供一個readObject的方法 以 約束關係保持安全性

如果一個類的物理表示法與它的邏輯數據內容有實質性的區別時,使用默認序列化就會有一下問題

  • 使這個類的導出API永遠束縛在該類的內部表示法上
  • 它會消耗過多的空間
  • 它會引起棧溢出

注意事項

  • 在確定將一個域做成非transient之前,請一定要確認它的值將是該對象邏輯狀態的一部分
  • 如果在讀取整個對象狀態的任何其他方法上強制任何同步,則也必須在對象序列化上強制這種同步

76.保護性的編寫readObject

簡介

  1. readObject方法相當於另一個公有的構造器
  2. readObject 可以說是將字節流作爲唯一參數
  3. 反序列化的時候,readObject如果不進行深拷貝、以及數據合法性驗證,就會導致生成的對象數據非法
  4. 不要使用writeUnsharedreadUnshared方法,因爲它們不安全()
  5. final類,構造函數以及readObject方法中,不能調用可重載的方法

建議

爲了編寫出健壯的readObject方法,有以下方針

  1. 對象應用域必須保持私有的類,要保護性的拷貝這些域中的每個對象
  2. 對於任何約束條件,如果檢查失敗,則拋出異常
  3. 如果整個對象圖在被反序列化後必須驗證,則應該使用ObjectInputValidation
  4. 無論是直接形式還是間接形式,都不要調用類中任何可被覆蓋的方法.

77.對於實例控制,枚舉類優先於readResolve

簡介

readResolve特性允許你用readObject創建的實例代替另一個實例.
如果依賴readResolve來進行單例控制,則引用類型的所有實例域都應該是transient(序列化會忽略該字段)的,

  1. 在1.5之後, readResolve就不再是可序列化的類中`維持實例控制的最佳方法了
  2. 最好使用枚舉來實現可序列化的實例控制,有JVM對此提供保障
  3. readResolve的可訪問性很重要.對於final和非final類其訪問性不同

小結

應該儘可能的使用枚舉類型來實施實例控制的約束條件.
否則就必須提供一個readResolve方法,並確保該類的所有實例域都爲基本類型,或者是 transient的.

78.考慮用序列化代理代替序列化實例

簡介

序列化代理

就是爲可序列化的類 設計一個私有的靜態嵌套類.精確的表示外圍類的實例的邏輯狀態,這個類就是序列化代理類.
源碼實例 :Period.java

示例中,通過內部類SerializationProxywriteReplacereadObject,readResolve等方法的運用,
就可以很方便的實現一個不受序列化攻擊威脅的類.
序列化代理模式的功能比保護性拷貝的更加強大,序列化代理模式允許反序列化實例有着與原始序列化實例不同的類.

序列化代理模式的兩個侷限

  1. 不能與可以被客戶端擴展的類兼容,也不能於對象圖中包含循環的某些類兼容.(如果企圖從序列化代理的readResolve方法內部調用對象中的方法,會得到 ClasscastException,因爲還沒有這兒對象,只有它的序列化代理類)
  2. 保護性拷貝的開銷更大

小結

  • 每當發現 要在一個不能被客戶端擴展的類上編寫readObjectwriteObject,就應該使用序列化代理模式
  • 最好手動指定serialVersionUID
  • 序列化並不保存靜態變量。
  • 要想將父類對象也序列化,就需要讓父類也實現Serializable 接口。如果父類實現的話的,就 需要有默認的無參的構造函數。
  • 在變量聲明前加上transient,可以阻止該變量被序列化到文件中,在被反序化後,transient 變量的值被設爲初始值,如 int 型的是 0
  • writeObjectreadObject 方法用於對敏感字段加密
  • 序列化兩次寫入相同的對象,第二次只會存儲引用關係
  • 序列化後存入的對象,修改字段值後繼續存入,兩次讀取都只會讀取到第一個值。
  • writeReplacewriteOjbect方法之前修改序列化的對象。
  • readresolvereadObject方法之後控制反序列化時得到的對象

擴展閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章