使类的实例可序列化非常简单,只需实现 Serializable 接口即可。因为这很容易做到,所以有一个普遍的误解,
认为序列化只需要程序员付出很少的努力。而事实上要复杂得多。虽然使类可序列化的即时代价可以忽略不计,但长
期代价通常是巨大的。
实现 Serializable 接口的一个主要代价是,一旦类的实现被发布,它就会降低更改该类实现的灵活性。 当类实现
Serializable 时,其字节流编码(或序列化形式)成为其导出 API 的一部分。一旦广泛分发了一个类,通常就需要永远
支持序列化的形式,就像需要支持导出 API 的所有其他部分一样。如果你不努力设计自定义序列化形式,而只是接受
默认形式,则序列化形式将永远绑定在类的原始内部实现上。换句话说,如果你接受默认的序列化形式,类中私有的
包以及私有实例字段将成为其导出 API 的一部分,此时最小化字段作用域(详见第 15 条)作为信息隐藏的工具,将
失去其有效性。
实现 Serializable 接口的第二个代价是,增加了出现 bug 和安全漏洞的可能性(第85项)。 通常,对象是用构造函
数创建的;序列化是一种用于创建对象的超语言机制。无论你接受默认行为还是无视它,反序列化都是一个「隐藏构
造函数」,其他构造函数具有的所有问题它都有。由于没有与反序列化关联的显式构造函数,因此很容易忘记必须让
它能够保证所有的不变量都是由构造函数建立的,并且不允许攻击者访问正在构造的对象内部。依赖于默认的反序列
化机制,会让对象轻易地遭受不变性破坏和非法访问(详见第 88 条)。
实现 Serializable 接口的第三个代价是,它增加了与发布类的新版本相关的测试负担。 当一个可序列化的类被修
改时,重要的是检查是否可以在新版本中序列化一个实例,并在旧版本中反序列化它,反之亦然。因此,所需的测试
量与可序列化类的数量及版本的数量成正比,工作量可能很大。你必须确保「序列化-反序列化」过程成功,并确保
它生成原始对象的无差错副本。如果在第一次编写类时精心设计了自定义序列化形式,那么测试的工作量就会减少
(详见第 87 和 90 条)。 |
|