Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

86. 非常谨慎地实现SERIALIZABLE接口
允许对类的实例进行序列化可以非常简单,只需将implements Serializable添加到类的声明中即可。因为这很容易做到,所以有一个普遍的误解,认为序列化只需要程序员付出很少的努力。事实要复杂得多。虽然使类可序列化的即时成本可以忽略不计,但长期成本通常是巨大的。
实现Serializable的一个主要成本是,一旦类的实现被发布,会降低更改该类实现的灵活性。当类实现Serializable时,其字节流编码(或序列化形式)成为其导出API的一部分。一旦这个类被广泛分发后,通常就需要永远支持序列化形式,就像需要支持导出API的所有其他部分一样。如果不努力设计自定义序列化形式(custom serialized form),而只是接受默认值,则序列化形式将永远绑定到类的原始内部表示上。换句话说,如果接受默认的序列化形式,类的私有和包级私有实例属性将成为其导出API的一部分,并且最小化属性访问的实践(条目 15)也失去其作为信息隐藏工具的有效性。
如果接受默认的序列化形式,日后更改类的内部表示,则会导致序列化形式中的不兼容更改。 尝试使用旧版本的类序列化实例并使用新版本对其进行反序列化(反之亦然)的客户端将遇到程序失败。 可以在保持原始序列化形式(使用ObjectOutputStream.putFields和ObjectInputStream.readFields)的同时更改内部表示,但这可能很困难并且在源代码中留下可见的缺陷。 如果选择将类序列化,应该仔细设计一个愿意长期使用的高质量序列化形式(条目 87,90)。 这样做会增加开发的初始成本,但值得付出努力。 即使是精心设计的序列化形式也会限制一个类的演变; 一个设计不良的序列化形式可能是后果严重的。
限制类的序列化演变的一个简单示例涉及到流的唯一标识符(stream unique identifiers),通常称为序列版本UID(serial version UIDs)。 每个可序列化的类都有一个与之关联的唯一标识号。 如果未通过声明名为serialVersionUID的静态fianl的long类型的来指定此数字,则系统会在运行时通过加密哈希函数(SHA-1)根据类的结构来自动生成它。 此值受类的名称,它实现的接口及其大多数成员(包括编译器生成的组合成(synthetic members)员)的影响。 如果更改任何这些内容,例如,通过添加一个便捷的方法,生成的序列版本UID就会更改。 如果未能声明序列版本UID,则兼容性将被破坏,从而导致运行时出现InvalidClassException异常。
实现Serializable的第二个成本是它增加了错误和安全漏洞的可能性(条目 85)。 通常,使用构造方法创建对象; 序列化是一种语言之外的创建对象的机制。 无论接受默认行为还是重写默认行为,反序列化都是一个“隐藏的构造方法”,与其他构造方法具有相同的问题。 因为没有与反序列化相关联的显式构造方法,所以很容易忘记必须确保它保证构造方法建立的所有不变性,并且它不允许攻击者访问构造中的对象的内部。 依赖于默认的反序列化机制,可以轻松地将对象置于不变性破坏和非法访问之外(第88项)。
实现Serializable的第三个成本是它增加了与发布新版本类相关的测试负担。 修改可序列化类时,重要的是检查是否可以序列化新版本中的实例可以在旧版本中反序列化,反之亦然。 因此,所需的测试量与可序列化类的数量和可能很大的发布数量的乘积成比。 必须确保“序列化——反序列化”过程成功,并确保它生成原始对象的忠实副本。 如果在首次编写类时仔细设计自定义序列化形式,那么测试的需求就会减少(条目 87,90)。
实现Serializable并不是一个轻松的决定。如果一个类要参与依赖于Java序列化来进行对象传输或持久性的框架,那么这一点是非常重要的。此外,它还极大地简化了将类作为必须实现Serializable的另一个类中的组件的使用。然而,与实现Serializable相关的成本很多。每次设计一个类时,都要权衡利弊。历史上,像BigInteger和Instant这样的值类实现了序列化,集合类也实现了Serializable。表示活动实体(如线程池)的类很少实现Serializable。
为继承而设计的类(条目 19)应该很少实现Serializable接口,接口也很少去继承它。 违反此规则会给继承类或实现接口的任何人带来沉重的负担。但是 有时候违反规则是合适的。 例如,如果一个类或接口主要存在于要求所有参与者实现Serializable的框架中,对类或接口来说,实现或继承Serializable是有意义的。
专为实现Serializable的继承而设计的类包括Throwable和Component。 Throwable实现Serializable,因此RMI可以从服务器向客户端发送异常。 Component实现Serializable,因此可以发送,保存和恢复GUI,但即使在Swing和AWT的全盛时期,这种机制在实践中很少使用。
如果实现了具有可序列化和可扩展的实例属性的类,则需要注意几个风险。如果实例属性的值上有任何不变行,关键是要防止子类重写finalize方法,该类可以通过重写finalize方法并声明它为final来实现这一点。否则,该类将容易受到终结器攻击(finalizer attacks)(条目 8)。最后,如果类的实例属性初始化为其默认值(整数类型为零,布尔值为false,对象引用类型为null),则会违反不变性,必须添加readObjectNoData方法:
// readObjectNoData for stateful extendable serializable classes
private void readObjectNoData() throws InvalidObjectException {
throw new InvalidObjectException("Stream data required");
}
在Java 4中添加了此方法,包括向现有可序列化类[Serialization,3.5]添加可序列化父类的极端情况。
关于不实现Serializable接口的决定有一点需要注意。 如果为继承而设计的类,此类不可序列化,则可能需要额外的努力编写可序列化的子类。 这种类的正常反序列化要求父类具有可访问的无参构造方法[Serialization,1.10]。 如果不提供这样的构造方法,则子类被迫使用序列化代理模式(serialization proxy pattern)(条目 90)。
内部类(条目 24)不应实现Serializable。 它们使用编译器生成的合成属性(synthetic fields)来保持对外围实例(enclosing instances)的引用,还保存来自外围作用范围的局部变量的值。这些属性与类定义的对应关系,以及匿名类和本地类的名称都是未指定的。 因此,内部类的默认序列化形式是不明确的。 但是,静态成员类可以实现Serializable。
总而言之,不要认为实现Serializable是简单的事情。除非类只在受保护的环境中使用,在这种环境中,版本永远不必相互操作,服务器永远不会暴露于不受信任的数据,否则实现Serializable是一项严肃的承诺,应该非常谨慎。如果类允许继承,则需要更加格外小心。
Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口的更多相关文章
- Effective Java 第三版——44. 优先使用标准的函数式接口
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——67. 明智谨慎地进行优化
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——66. 明智谨慎地使用本地方法
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——83. 明智谨慎地使用延迟初始化
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective Java 第三版——13. 谨慎地重写 clone 方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——48. 谨慎使用流并行
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- 使用Chrome浏览器设置XX-net的方法
以下介绍使用Chrome浏览器设置XX-net的方法 1.下载并安装谷歌浏览器. 2.打开https://github.com/XX-net/XX-Net/blob/master/code/d ...
- Codeforces 948D Perfect Security 【01字典树】
<题目链接> 题目大意: 给定两个长度为n的序列,可以改变第二个序列中数的顺序,使得两个序列相同位置的数异或之后得到的新序列的字典序最小. 解题分析: 用01字典树来解决异或最值问题.因为 ...
- Django之ORM操作总结
Django之ORM总结 表结构 from django.db import models # 一对多:班级与学生 # 多对多:班级与老师 # Create your models here. #创建 ...
- Typescript 常见的几种函数重载方法详解与应用示例
所谓的重载,其实就是使用相同的函数名,传入不同数量的参数或不同类型的参数,以此创建出多个方法或产生不同结果. 1. 最常见的,也就是根据定义傻瓜式地判断参数类型与数量 function showPer ...
- 团队协作统一vue代码风格,vscode做vue项目时的一些配置
1. 安装Vetur 扩展 主要是用于让vscode能识别vue文件,对vue代码进行高丽处理,并且它内置了一些代码格式化的设置 2. 安装ESLint 如果你的项目已经开启了eslint规范, 再有 ...
- (Zero XOR Subset)-less-线性基
(Zero XOR Subset)-less 题意 :把n个数分成多个集合,要求 不能有集合为空,最终不能有非空子集合异或值为0,尽可能划分的多一些. 思路 :非法情况就只有 n个数异或 为0,其他的 ...
- A - Character Encoding HDU - 6397 - 方程整数解-容斥原理
A - Character Encoding HDU - 6397 思路 : 隔板法就是在n个元素间的(n-1)个空中插入k-1个板,可以把n个元素分成k组的方法 普通隔板法 求方程 x+y+z=10 ...
- NOIP2017 题解(给自己看的) --有坑要填
目录 D1T1精妙证明: D1T3 D2T2 几道水题就不写了.... D1T1精妙证明: 把ax+by = z 的z按照模a剩余系分类 由于\((a,b)=1\)所以对于每个\(k\in[0, a) ...
- 多AG自动生成apk说明
-----------自动生成apk说明.txt 1017202130405658626378 自动实现思路几个变化的地方 就是 第一次放入文件10, 修改apktype 100130 10生成apk ...
- GCC卡常
#pragma GCC optimize("Ofast,no-stack-protector") #pragma GCC optimize("-funsafe-loop- ...