第75条:考虑使用自定义的序列化形式
如果这个类实现了Serializable接口,并且使用了默认的序列化形式,你就永远无法彻底摆脱那个应该丢弃的实现了。它将永远牵制住这个类的序列化形式。
如果没有先认真考虑默认的序列化形式是否适合,则不要贸然接受。一般来将,只有当你自行设计的自定义序列化形式与默认的序列化形式基本相同时,才能接受默认序列化形式。
默认的序列化形式描述了改对象内部所包含的数据,以及每一个可以从这个对象到达其他对象的内部数据。它也描述了所有这些对象被链接起来后的拓扑结构。对于一个对象来说,理想的序列化形式应该只包含改对象所表示的逻辑数据,而逻辑数据与物理表示法应该是各自独立的。
如果一个对象的物理表示法等同于它的逻辑内容,可能就适合使用默认的序列化形式。
即使你确定了默认的序列化形式是适合的,通常还必须提供一个readObject方法以保证约束关系和安全性。
当一个对象的物理表示法与它的逻辑内容有实质性区别时,使用默认序列化形式会有以下四个缺点:
1 它是这个类的导出API永远地束缚在该类的内部类表示法上。
2 它会消耗过多的空间。例如实现细节不需要记录在序列化中。
3 它会消耗过多的时间。序列化逻辑并不了解对象图的拓扑关系,所以它必须要经过一个昂贵的图遍历(traversal)过程。
4 它会引起栈溢出。默认的序列化过程要对对象执行一次递归遍历,即使对中等规模的对象图,这样的操作也可能引起栈溢出。
transient 修饰符表明这个实例域将从一个类的默认序列化形式中省略掉。
如果所有的实例域都是瞬时的(transient),从技术角度而言,不调用defaultWriteObject和defaultReadObject也是允许的,但是不推荐这么做。即使所有的实例域都是transient的,调用defaultWriteObject也会影响该类的序列化形式,从而极大地增强灵活性。这样得到的序列化形式允许再以后的发行版本中增加非transient的实例域,并且还能保持向前或者向后兼容性。
无论你是否使用默认的序列化形式,当defaultWriteObject方法被调用时,每一个未被标记为transient的实例域都会被序列化。在决定一个域做成非transient的之前,请一定要确信它的值将是该对象逻辑状态的一部分。
如果你正在使用默认的序列化形式,并且把一个或多个域标记为transient,则要记住,当他被反序列化的时候,这些域将被初始化为它们的默认值。引用类型 , null,数值型 ,0 /0.0 ,boolean型 , false。如果这些值对于任何瞬时状态的属性都不可接受,则必须提供一个readObject方法,该方法调用defaultReadObject方法,然后将瞬时状态的属性恢复为可接受的值(76条)。或者,这些属性可以在第一次使用时进行延迟初始化(71条)。
如果在读取整个对象状态的其他任何方法上强制任何同步,则也必须在对象序列化上强制这种同步。
不管你选择了哪种序列化形式,都要为自己编写的每个可序列化类声明一个显示的序列版本UID。 不要更改序列版本UID,除非想破坏与类的所有现有序列化实例的兼容性。