序列化技术为远程通信提供了标准的线路级(wire-level)对象表示法,也为JavaBeans组件结构提供了表混的持久化数据格式。
实现Serializable接口的最大代价是:一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。 如果一个类实现了Serializable接口,它的字节流编码就变成了它的导出的API的一部分。一旦这个类被广泛使用,往往必须永远支持这种序列化形式。如果你接受了默认的序列化形式,这个类中私有的和包级私有的实例域都将变成导出的API的一部分,这不符合“最低限度地访问域”的实践准则。
序列化会使类的演变受到限制。例如,流的唯一标识符(stream unique identifier,或者被称为序列版本UID serial version UID)。每个可序列化的类都有一个唯一的标识号与它相关联。如果你没有定义private staic final 的 serialVersionUID,系统会根据这个类来调用一个复杂的运算过程,从而在运行时产生该标识号。标识号的产生会受到类名称、所实现的接口名称和所有public、protected成员名称影响。如果类发生了变化,又没有定义serialVersionUID,兼容性会遭到破坏,在运行时导致InvalidClassException。
实现Serializable接口的第二个代价是:增加了出现bug和安全漏洞的可能性。 序列化机制是一种语言之外的对象创建机制(extralinguistic mechanism),是一种隐藏的构造器。反序列化过程中,必须也要保证所有“由真正的构造器建立起来的约束关系”,并且不允许攻击者访问正在构造过程中的对象的内部信息。依靠默认的反序列化机制,很容易使对象的约束关系遭到破坏,以及遭受到非法访问。
实现Serializable接口的第三个代价是:随着类发行新的版本,相关测试负担也增加了。当一个可被序列化的类被修订的时候,需要检查*“新版本中序列化一个实例,旧版本中能够成功反序列化,反之亦然”*。 测试的工作量与 “可序列化类的数量和发行版本号的乘积” 成正比。既要确保“序列-反序列化”过程的成功,也要确保产生的对象真的是原始对象的复制品。
根据经验,Date和BigInteger这样的类应该实现Serializable,大多数集合类也应该如此。代表活动实体的类,比如线程池(thread pool),一般不应该实现Serializable。
为了继承而设计的类应该尽可能少地去实现Serializable接口,用户的接口也应该尽可能少地继承Serializable接口。 但是有写情况下违反这条规则是合适的:如果一个类或者接口主要目的是为了参与到某个框架中,该框架所要求的所有参与者都不必须实现Serializable接口,那么,此时implement或者extend Serializable接口是合适的。为了继承而设计的类中,真正实现了Serializable接口的有Trowable(RMI异常可以从服务器端传到客户端)、Component(GUI可以被发送、保存和恢复)和HttpServlet(session state可以被缓存)抽象类。
如果类有一些约束条件,当类的实例域被初始化成它们的默认值时,就会违背这些约束条件,此时必须给这个类添加readObjectNoData方法。
如果一个类是不可序列化的,那个它的子类也是不可序列化的。特别地,如果超类没有提供可访问的无参构造器,子类也不可能做到可序列化。因此,对于为继承而设计的不可序列化的类,你应该提供一个无参构造器。
最好在所有约束关系都已经建立的情况下再创建对象。如果为了建立这些约束关系需要客户端提供一些数据,这实际上就排除了使用无参构造器的可能性。盲目地为一个类增加无参构造器和单独的初始化方法,而它的约束关系仍由其他的构造器来建立,这样做会使该类的状态空更加复杂,并且增加出错的可能性。
如下,可以给“不可序列化但可扩展的类”增加无参构造器,同时避免以上的不足。增加一个protected的构造器和一个初始化方法,初始化方法和正常的构造器有相同的参数。并且也能建立起同样的约束关系。AbstractFoo 中所有的public和protected的实例方法开始都会checkInit。这样就可以确保如果有编写不好的子类没有初始化实例,该方法调用直接就失败。init域是一个原子引用,用来确保对象的完整性。
package rule74;
import java.util.concurrent.atomic.AtomicReference;
public abstract class AbstractFoo {
private int x;
private int y;
private enum State {NEW, INITIALIZING, INITIALIZED};
private final AtomicReference<State> init = new AtomicReference<>(State.NEW);
public AbstractFoo(int x, int y) {
initialize(x, y);
}
protected AbstractFoo() {
}
protected final void initialize(int x, int y) {
if (!init.compareAndSet(State.NEW, State.INITIALIZING)) {
throw new IllegalStateException("Already initialized");
}
this.x = x;
this.y = y;
init.set(State.INITIALIZED);
}
protected final int getX() {
checkInit();
return x;
}
protected final int getY() {
checkInit();
return y;
}
private void checkInit() {
if (init.get() != State.INITIALIZED) {
throw new IllegalStateException("Uninitialized");
}
}
}
package rule74;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Foo extends AbstractFoo implements Serializable {
private static final long serialVersionUID = 185683586095L;
public Foo(int x, int y) {
super(x, y);
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int x = s.readInt();
int y = s.readInt();
initialize(x, y);
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(getX());
s.writeInt(getY());
}
}
内部类不应该实现Serializable。内部类使用编译器产生的合成域(synthetic field)来保存指向外围实例(enclosing instance)的引用,以及保存来自外围作用域的局部变量的值。“这些类如何对应到类定义中”并没有明确的规定,就好像没有指定匿名类和局部类的名称一样。因此,内部类的默认序列化形式是定义不清楚的。但是,静态成员类是可以实现Serializable接口的。