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