第 85 条: 首选 Java 序列化的替代品
序列化是危险的,反序列化有漏洞,应该避免。
使用跨平台的结构化数据替代,例如 JSON 或 protobuf。
或者不反序列化不受信任的数据。
如果必须反序列化,使用对象反序列化过滤,并避免编写可序列化的类。
第 86 条:谨慎地实现 Serializable 接口
Serializable 的成本
- 降低了发布后更改类的实现的灵活性。
- 如果未能声明串行版本 UID,则会破坏兼容性
- 增加了错误和安全漏洞的可能性
- 增加了与发布新版本类相关的测试负担。
考虑点
- 如果一个类将要加入到某个框架中,并且该框架依赖于序列化来实现对象传输或者持久化,对于这个类来说,实现 Serializable 接口就非常有必要。
- 为继承而设计的类应该很少实现 Serializable,接口应该很少扩展它。
- 内部类不应该实现 Serializable
第 87 条:考虑使用自定义的序列化形式
一般来讲,只有当你自行设计的自定义序列化形式与默认的形式基本相同时,才能接受默认的序列化形式。
用户可以通过 writeObject() 和 readObject() 自定义序列化和反序列化逻辑。对一些敏感信息加密的逻辑也可以放在此。
无论你是否使用默认的序列化形式,如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步。
第 88 条:保护性地编写 readObject 方法
对于非 final 的可序列化类,在 readObject 方法和构造器之间还有其他类似的地方,readObject 方法不可以调用可被覆盖的方法,无论是直接调用还是间接调都不可以。如果违反了该规则,并且覆盖了该方法,被覆盖的方法将在子类的状态被反序列化之前先运行。程序很可能会失败。
第 89 条:对于实例控制枚举类型优先于 readResolve
如果这个类的声明中加上了“implements Serializable”的字样,它就不再是一个 Singleton。
如果依赖 readResolve 进行实例控制,带有对象引用类型的所有实例域则都必须声明为 transient 的。
将一个可序列化的实例受控的类编写成枚举,就可以绝对保证除了所声明的常量之外,不会有别的实例。
第 90 条:考虑用序列化代理代替序列化实例
首先,为可序列化的类设计一个私有的静态嵌套类,精确地表示外围类的实例的逻辑状态。这个嵌套类被称作序列化代理(serialization proxy),它应该有一个单独的构造器,其参数类型就是那个外围类。这个构造器只从它的参数中复制数据:它不需要进行任何一致性检查或者保护性拷贝。按设计,序列代理的默认序列化形式是外围类最好的序列化形式。外围类及其序列代理都必须声明实现 Serializable 接口。
每当你发现自己必须在一个不能被客户端扩展的类上编写 readObject 或者 riteObject 方法的时候,就应该考虑使用序列化代理模式。要想稳健地将带有重要约束条件的对象序列化时,这种模式可能是最容易的方法。