首页 > 学院 > 开发设计 > 正文

[Effective Java]第十一章 序列化

2019-11-14 22:45:54
字体:
来源:转载
供稿:网友
[Effective java]第十一章 序列化声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.VEVb.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.VEVb.com/jiangzhengjun/p/4255724.html 第十一章 序列化 74、 谨慎地实现Serializable接口

实现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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表