第74条:谨慎地实现Serializable
1)实现Serializable接口而付出的最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。
2)第二个代价是,它增加了出现Bug和安全漏洞的可能性。(会被反序列化)
3)第三个代价是,随着类发行新的版本,相关的测试负担也增加了。(要测试序列化与反序列化是否正常)
4)为了继承而设计的类应该尽可能少地去实现Serializable接口,用户的接口也应该尽可能少地继承Serializable接口。
总而言之,千万不要认为实现Serializable接口会很容易。除非一个类在用了一段时间之后会被抛弃,否则,实现Serializable接口就是个很严肃的承诺,必须认真对待。如果一个类是为了继承而设计的,则更加需要加倍小心。对于这样的类而言,在“允许子类实现Serializable接口”或“禁止子类实现Serializable接口”两者之间的一个折衷设计方案是,提供一个可访问的无参构造器。这种设计方案允许(但不要求)子类实现Serializable接口。
第75条:考虑使用自定义的序列化形式
如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受。
即使你确定了默认的序列化形式是合适的,通常还必须提供了一个readObject方法以保证约束关系和安全性。
总而言之,当你决定要将一个类做成可序列化的时候,请仔细考虑应该采用什么样的序列化形式。只有当默认的序列化形式能够合理地描述对象的逻辑状态时,才能使用默认的序列化形式;否则就要设计一个自定义的序列化形式,通过它合理地描述对象的状态。你应该分配足够多的时间来设计类的序列化形式,就好像分配足够多的时间来设计它的导出方法一样。正如你无法在将来的版本中去掉导出方法一样,你也不能去掉序列化形式中的域,它们必须被永久地保留下去,以确保序列化兼容性(serialization compalibility)。选择错误的序列化形式对于一个类的复杂性和性能都会有永久的负面影响
第76条:保护性地编写readObject方法
总而言之,每当你编写readObject方法的时候,都要这样想:你正在编写一个公有的构造器,无论给它传递什么样的字节流,它都必须产生一个有效的实例。不要假设这个字节流一定代表着一个真正被序列化过的实例。虽然在本条目的例子中,类使用了默认的序列化形式,但是,所有讨论到的有可能发生的问题也同样适用于自定义序列化形式的类。下面以摘要的形式给出一些指导方针,有助于编写出更加健壮的readObject方法:
1)对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别。
2)对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后。
3)如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口。
4)无论是直接方式还是间接方式,都不要调用类中任何可被覆盖的方法。
第77条:对于实例控制,枚举类型优先于readResolve
单例被反序列化的问题
如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的。
你应该尽可能地使用枚举类型来实施实例控制的约束条件。如果做不到,同时又需要一个既可序列化又是实例受控(instance-controlled)的类,就必须提供一个readResolve方法,并确保该类的所有实例域都为基本类型,或者是transient的。
参考:http://book.51cto.com/art/200901/106063.htm
第78条:考虑用序列化代理代替序列化实例
1)序列化代理
就是为可序列化的类 设计一个私有的静态嵌套类.精确的表示外围类的实例的逻辑状态,这个类就是序列化代理类.
源码实例 :https://gitee.com/charjay/effective.git
示例中,通过内部类SerializationProxy和writeReplace及readObject,readResolve等方法的运用,
就可以很方便的实现一个不受序列化攻击威胁的类.
序列化代理模式的功能比保护性拷贝的更加强大,序列化代理模式允许反序列化实例有着与原始序列化实例不同的类.
2)序列化代理模式的两个局限
1.不能与可以被客户端扩展的类兼容,也不能于对象图中包含循环的某些类兼容.(如果企图从序列化代理的readResolve方法内部调用对象中的方法,会得到 ClasscastException,因为还没有这儿对象,只有它的序列化代理类)
2.比保护性拷贝的开销更大
小结
1)每当发现 要在一个不能被客户端扩展的类上编写readObject或writeObject,就应该使用序列化代理模式
2)最好手动指定serialVersionUID
3)序列化并不保存静态变量。
4)要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类实现的话的,就 需要有默认的无参的构造函数。
5)在变量声明前加上transient,可以阻止该变量被序列化到文件中,在被反序化后,transient 变量的值被设为初始值,如 int 型的是 0
6)writeObject 和 readObject 方法用于对敏感字段加密
7)序列化两次写入相同的对象,第二次只会存储引用关系
8)序列化后存入的对象,修改字段值后继续存入,两次读取都只会读取到第一个值。
9)writeReplace在writeOjbect方法之前修改序列化的对象。
10)readresolve在readObject方法之后控制反序列化时得到的对象