收藏了!70%的程序员都不知道的序列化细节,清晰明了( 二 )


答案
默认的序列化形式描述了该对象内部所包含的数据 , 以及每一个可以从这个对象到达的其他对象的内部数据 , 即完整了描述了所有对象被连接起来的拓扑结构 。 对于一个对象来说 , 理想的序列化形式应该只包含该对象所表示的逻辑数据 , 而逻辑数据和物理表示应该是相互独立 。 也就是说 , 如果一个对象的物理表示等同于它的逻辑内容的话 , 就适合使用默认的序列化形式 。有这样一个例子 public class Name implements Serializable { private final String lastName; private final String firstName; private final String middleName; ... ... }
从逻辑的角度而言 , Name类可以简单的由lastName、firstName以及middleName三个属性来进行表示 , 也就是说 , 这三个属性可以精确的反映出它的逻辑内容 。 因此 , 这种情况可以采用默认的序列化形式 , 同样也需要在readObject中进行参数有效性检测和保护性拷贝 。使用默认序列化形式 , 当一个或多个域字段被标记为transient 时 , 如果要进行反序列化 , 这些域字段都将被初始化为其类型默认值 , 如对象引用域被置为null , 数值基本域的默认值为0 , boolean域的默认值为false 。 如果这些值不能被任何transient 域所修饰 , 你就必须提供一个readObject方法 。 它首先调用defaultReadObject , 然后再把这些transient 域进行恢复为之前的初始值;同样的 , 在序列化过程中 , 被transient修饰的实例域会被省略掉 在序列化过程中 , 虚拟机会试图调用对象类里的writeObject() 和readObject() , 因此可以在readObject和writeObject方法中实现自己的序列化逻辑 。 就算没有实现特定的逻辑也应该调用默认的ObjectOutputStream.defaultWriteObject() 和ObjectInputStream.defaultReadObject()方法 , 这样就可以保证向前或者向后的兼容性; 无论你选择了哪种序列化形式 , 都要为自己编写的每个可序列化的类声明一个显式的序列版本UID 。 这样可以避免序列版本UID成为潜在的不兼容根源 , 同时也会带来小小的性能好处 , 因为不需要去算序列版本UID 。
结论
当你决定将一个类设计成可序列化的时候 , 就应该详细考虑应该采用什么样的序列化形式 。 只有当默认的序列化形式能够合理的描述对象的逻辑状态时 , 才能使用默认的序列化形式 。 否则就要设计一个自定义的序列化形式 , 通过它合理的描述出对象的状态 。
谨慎使用readObject方法问题
为了让程序更加安全可靠 , 需要针对可变域在构造器和访问方法中进行保护性拷贝 , 例如下面的代码: public final static class Period{ private final Date start; private final Date end; public Period(Date start, Date end){ this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if(this.start.compareTo(this.end)>0){ throw new IllegalArgumentException(start + "after" +end); } } public Date getStart() { return new Date(start.getTime()); } public Date getEnd() { return new Date(end.getTime()); } }
但是如果将这个类进行序列化的时候 , 就可能这个类会出现不满足start和end的约束关系了 , 那么 , 应该怎样保证在序列化的时候也能保障对象的关键约束关系?
答案
除了构造器构造对象外 , 反序列化也是一种构造对象的方式 , 因此 , 也需要在构造对象的时候进行参数有效性检查以及保护性拷贝 。 所以在readObject方法也需要确保Period的关键约束不变以及保持它的不可变性:
private void readObject(ObjectInputStream s)throws IOException, ClassNotFoundException {s.defaultReadObject();// Defensively copy our mutable componentsstart = new Date(start.getTime());end = new Date(end.getTime());// Check that our invariants are satisfiedif (start.compareTo(end) > 0)throw new InvalidObjectException(start +" after "+ end);}}