实现Serializable接口而付出的最大代价是,一旦一个类被发布,就大降低了“改变这个类的实现”的灵活性。如采用默认的序列化方式时(仅实现Serializable),且没有在一个名为serialVersionUID的私有静态final的long域显示地指定该标识号,则如果类改变了,则会导致不兼容。
实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性。反序列化过程不是调用原有的构造器,所以你很容易忘记要确保:反序列化过程必须也要保证所有“由真正的构造器建立起来约束关系”,并且不允许攻击者访问正在构造过程上的对象的内部信息,依靠默认的反序列化机制,很容易对象的约束关系遭到破坏,以及遭受非法访问(第76条)。
实现Serializable的第三个代价是,随着类发行新的版本,相关的测试负担也增加了。
实现Serializable接口并不是一个很轻松就可以做出的决定。根据经验,如Data和BigInteger这样的的值类应该实现Serializable,大多数的集合类也是应该如此。代表活动实体的类,如线程池,一般不应该实现Serializable。
为了继承而设计的类,应该尽可能少地去实现Serializable接口,用户的接口也应该尽可能少地继承Serializable接口。如果违反了这条规则,扩展这个类或者实现这个接口的程序员就会背上沉重的负担。然后在有些情况下是合适的,如,这个类或接口主要是为了参与到某个框架中,而该框架要求所有参与者都实现Serializable接口,则实现Serializable是有意义的。
对于为继承而设计的不可序列化的类,你应该考虑提供一个无参构造器,因为他的子类可能是可序列化的,一旦子类实现Serializable接口,则在反序列化时会调用调用父类的无参构造器。最好在所有约束关系都已经建立的情况下再创建对象。下面是一个父类不可序列化,而子类可序列化的建议做法:
//不可序列化
publicabstractclassAbstractFoo {
PRivateintx,y;//状态
//枚举字段用于跟踪初始化于哪种状态:NEW-新建,INITIALIZING-正在初始化,INITIALIZED-初始化完成
privateenumState{
NEW,INITIALIZING,INITIALIZED
};
/*
*初始化到哪种状态了
*注意,init是一个原子引用。在遇到特定的情况下,确保对象的完整性是很重要的。如果没有这样的防范,
*万一有个线程要在某个实例上调用initialize,而另一个线程又要企图使用这个实例,第二个线程就有
*可能看到这个实例处于不一致的状态。这种模式利用compareAndSet方法来操作枚举的大孩子引用。
*/
privatefinalAtomicReference<State>init=newAtomicReference<State>(State.NEW);
//该域是第一版中的
// private boolean initialized = false;//第一版
publicAbstractFoo(intx,inty) {
initialize(x, y);
}
//此构造和下面的方法让子类的readObject方法来初始化我们的状态
protectedAbstractFoo() {//受保护的构造器
}
protectedfinalvoidinitialize(intx,inty) {
//compareAndSet(V expect, V update):如果当前值==预期值expect,
//则以原子方式将该值设置为给定的更新值update。如果还未初始化时,将初始状态设为:INITIALIZING
if(!init.compareAndSet(State.NEW, State.INITIALIZING))
//if (initialized)//第一版
thrownewIllegalStateException("Already initialized");
this.x= x;
this.y= y;
//构造完后设置成完成状态
init.set(State.INITIALIZED);
// initialized=true;//第一版
}
//这些方法提供了访问内部状态,因此可以
//通过子类的writeObject方法来手动序列化。
protectedfinalintgetX() {
checkInit();
returnx;
}
protectedfinalintgetY() {
checkInit();
returny;
}
//所有共有的与保护的实例方法都必须调用
privatevoidcheckInit() {
// if(!initialized)//第一版
if(init.get() != State.INITIALIZED)
thrownewIllegalStateException("Uninitialized");
}
//其余的略
}
//继承不可序列化父类,但已自己实现Serializable接口
publicclassFooextendsAbstractFooimplementsSerializable {
privatevoidreadObject(ObjectInputStream s)throwsIOException,
ClassNotFoundException {
s.defaultReadObject();
//手工反序列化和初始化父类的状态
intx = s.readInt();
inty = s.readInt();
initialize(x, y);
}
privatevoidwriteObject(ObjectOutputStream s)throwsIOException {
s.defaultWriteObject();
//手工序列化父类的状态
s.writeInt(getX());
s.writeInt(getY());
}
// Constructor does not use the fancy mechanism
publicFoo(intx,inty) {
super(x, y);
}
privatestaticfinallongserialVersionUID= 1856835860954L;
}
内部类不应该实现Serializable接口,它们使用编译器产生的合成域来保存指向外围实例的引用,以保存来自外围作用域的局部变量的值。“这些域如何对应到类定义中”并没有明确的规定,就好像匿名类和局部类的名称一样,它们都是由编译器临时产生的,我们不能引用它们。因此,内部类默认序列化形式是定义不清楚的。然而,静态成员类却可以实现Serializable接口。
总之,实现Serializable接口不是容易的事。实现Serializable接口是个严肃的承诺,必须认真对待。如果类是为了继承使用的,则一定要提供一个默认构造器,以防止子类序列化。
75、 考虑使用自定义的序列化形式Word-spacing: 0
新闻热点
疑难解答