Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口

时间:2023-01-05 01:12:00

Tips

书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code

注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口

86. 非常谨慎地实现SERIALIZABLE接口

允许对类的实例进行序列化可以非常简单,只需将implements Serializable添加到类的声明中即可。因为这很容易做到,所以有一个普遍的误解,认为序列化只需要程序员付出很少的努力。事实要复杂得多。虽然使类可序列化的即时成本可以忽略不计,但长期成本通常是巨大的。

实现Serializable的一个主要成本是,一旦类的实现被发布,会降低更改该类实现的灵活性。当类实现Serializable时,其字节流编码(或序列化形式)成为其导出API的一部分。一旦这个类被广泛分发后,通常就需要永远支持序列化形式,就像需要支持导出API的所有其他部分一样。如果不努力设计自定义序列化形式(custom serialized form),而只是接受默认值,则序列化形式将永远绑定到类的原始内部表示上。换句话说,如果接受默认的序列化形式,类的私有和包级私有实例属性将成为其导出API的一部分,并且最小化属性访问的实践(条目 15)也失去其作为信息隐藏工具的有效性。

如果接受默认的序列化形式,日后更改类的内部表示,则会导致序列化形式中的不兼容更改。 尝试使用旧版本的类序列化实例并使用新版本对其进行反序列化(反之亦然)的客户端将遇到程序失败。 可以在保持原始序列化形式(使用ObjectOutputStream.putFieldsObjectInputStream.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接口的更多相关文章

  1. Effective Java 第三版——44. 优先使用标准的函数式接口

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Effective Java 第三版——67&period; 明智谨慎地进行优化

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  3. Effective Java 第三版——66&period; 明智谨慎地使用本地方法

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  4. Effective Java 第三版——83&period; 明智谨慎地使用延迟初始化

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  5. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  6. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  7. Effective Java 第三版——13&period; 谨慎地重写 clone 方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——48&period; 谨慎使用流并行

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版——19&period; 如果使用继承则设计,并文档说明,否则不该使用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. sql杀死进程

    查询SQL所有的链接  并可以查看连接当前正在做什么操作..使用的什么语句.. SELECT spid, blocked, DB_NAME(sp.dbid) AS DBName, program_na ...

  2. 阅读 Linux 内核源码

    阅读Linux Kernel Source Code 假如你在Linux系统下面阅读Linux内核源代码,那么需要准备一些工具. ①Linux的内核源码 内核源码的下载地址:Index of /pub ...

  3. 手机APP上下滚动翻页效果

    //页面初期加载时        $(document).ready(function () { //加载第一页            LoadList(); //滚动换页            $( ...

  4. Swift - 类型属性(类静态属性)和类方法(类静态方法)

    1,结构体struct和枚举enum的静态属性,静态方法使用static关键字 1 2 3 4 5 6 7 8 9 10 struct Account {      var amount : Doub ...

  5. C&num;操作WORD换行

    appWord.ActiveDocument.Bookmarks[bookMark].Select(); Word.Selection wordSelection = appWord.ActiveDo ...

  6. 说一说MVC的Authentication过滤&lpar;四&rpar;

    前沿: 一般情况下,在我们做访问权限管理的时候,会把用户的正确登录后的基本信息保存在Session中,以后用户每次请求页面或接口数据的时候,拿到 Session中存储的用户基本信息,查看比较他有没有登 ...

  7. CMake&colon; Could NOT find PkgConfig

    转自http://www.ogre3d.org/forums/viewtopic.php?f=22&t=78490 Looking for OGRE... Could NOT find Pkg ...

  8. Sql Server外键约束

    一.添加约束(级联删除) 1.创建表结构时添加 create table UserDetails(id int identity(1,1) primary key,name varchar(50) n ...

  9. Python os&period;walk文件遍历

    os.walk(top, topdown=True, onerror=None, followlinks=False) 可以得到一个三元tupple(dirpath, dirnames, filena ...

  10. window7&plus;wamp环境配置Oracle数据库连接

    最近开发需要使用的oracle数据库!翻看了PHP手册,也在网上找了些帖子!功夫不负有心人,花费了四五个小时的时间,终于找到了Oracle的配置方法.下面就讲解下如何配置Oracle数据库连接吧! 附 ...