Effective Java : 序列化

来源:互联网 发布:淘宝主营类目 大类目 编辑:程序博客网 时间:2024/05/21 22:57

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方法之后控制反序列化时得到的对象

扩展阅读

  • 关于 Java 对象序列化您不知道的 5 件事
  • Java 序列化的高级认识
0 0