序列化

74,谨慎地实现Serializable接口
  • 实现Serializable接口而付出的最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。

如果你接受了默认的序列化形式,并且以后又要改变这个类的内部表示法,结果可能导致序列化形式的不兼容。

如果你没有声明一个显示的序列版本UID,兼容性将会遭到破坏,在运行时导致invalidClassExeception。

  • 实现Serializable接口的第二个代价是,它增加了出现bug和安全漏洞的可能性。

反序列化机制是一个“隐藏的构造器”,具备与其他构造器共同的特点。反序列化的过程必须要保证所有“由真正的构造器建立起来的约束关系”,并且不允许攻击者访问正在构造过程中的对象的内部信息。

  • 实现Serializable接口的第三个代价是,随着类发行新的版本,相关的测试负担也增加了。

为了继承而设计的类应该尽可能少的去实现Serializable接口,用户的接口也应该尽可能少的继承Serializable接口。如果一个类或者接口存在的目的主要是为了参与到某个框架中,改框架要求所有的参与者都必须实现Serializable接口,那么实现Serializable接口就非常有意义。

如果超类没有提供可供访问的无参构造器,子类也不可能做到可序列化。因此,对于为了继承而设计的不可序列化的类,你应该考虑提供一个午餐构造器。

75,考虑使用自定义的序列化形式

如果这个类实现了Serializable接口,并且使用了默认的序列化形式,你就永远无法彻底摆脱那个应该丢弃的实现了。它将永远牵制住这个类的序列化形式。

如果没有先认真考虑默认的序列化形式是否适合,则不要贸然接受。一般来将,只有当你自行设计的自定义序列化形式与默认的序列化形式基本相同时,才能接受默认序列化形式。

默认的序列化形式描述了改对象内部所包含的数据,以及每一个可以从这个对象到达其他对象的内部数据。它也描述了所有这些对象被链接起来后的拓扑结构。对于一个对象来说,理想的序列化形式应该只包含改对象所表示的逻辑数据,而逻辑数据与物理表示法应该是各自独立的

如果一个对象的物理表示法等同于它的逻辑内容,可能就适合使用默认的序列化形式。

即使你确定了默认的序列化形式是适合的,通常还必须提供一个readObject方法以保证约束关系和安全性。

当一个对象的物理表示法与它的逻辑内容有实质性区别时,使用默认序列化形式会有以下四个缺点:

  • 它是这个类的导出API永远地束缚在该类的内部类表示法上。
  • 它会消耗过多的空间。例如实现细节不需要记录在序列化中。
  • 它会消耗过多的时间。序列化逻辑并不了解对象图的拓扑关系,所以它必须要经过一个昂贵的图遍历过程。
  • 它会引起栈溢出。默认的序列化过程要对对象执行一次递归遍历,即使对中等规模的对象图,这样的操作也可能引起栈溢出。

如果所有的实例域都是瞬时的,从技术角度而言,不调用defaultWriteObject和defaultReadObject也是允许的,但是不推荐这么做。即使所有的实例域都是transient的,调用defaultWriteObject也会影响该类的序列化形式,从而极大地增强灵活性。这样得到的序列化形式允许再以后的发行版本中增加非transient的实例域,并且还能保持向前或者向后兼容性。

无论你是否使用默认的序列化形式,当defaultWriteObject方法被调用时,每一个未被标记为transient的实例域都会被序列化。在决定一个域做成非transient的之前,请一定要确信它的值将是该对象逻辑状态的一部分。

如果在读取整个对象状态的其他任何方法上强制任何同步,则也必须在对象序列化上强制这种同步。

不管你选择了哪种序列化形式,都要为自己编写的每个可序列化类声明一个显示的序列版本UID。

76,保护性地编写readObject方法

当一个对象被反序列化的时候,对于客户端不应该有的对象引用,如果哪个域包含了这样的对象引用,就必须要做保护性拷贝,这非常重要的。私有可变组件一定进行保护性拷贝。

注意,保护性拷贝是在有效性检查之前进行的。

  • 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象,不可变类的可变组件就属于这一类别。
  • 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后。
  • 如果整个对象被反序列化之后必须进行验证,就改使用ObjectInputValidation接口。
  • 无论是直接还是间接方式,都不要调用类中任何可被覆盖的方法。
77,对于实例控制,枚举类型优先于readResolve

readResolve特性允许你用readObject创建的实例代替另一个实例。

事实上,如果依赖readResolve进行实例控制,带有对象引用类型的所有实例类型则都必须声明为transient的。

如果把readResolve方法放在一个final类上,它就应该是私有的。如果把它放在非final类上,就必须认真考虑它的可访问性。如果是私有的就不适用于任何子类。如果它是包级私有的,就只适用于同一个包中的子类。如果是受保护或共有的,就适用于所有没有覆盖它的子类,如果此时子类没有覆盖它,对序列化过的子类实例进行反序列化,就会产生一个超类实例,这样可能导致ClassCastException异常。

78,考虑用序列化代理代替序列化实例

首先,为可序列化的类设计一个私有的静态嵌套类,精确地表示外围类的实例的逻辑状态。这个嵌套类被称为序列化代理,它应该有一个单独的构造器,其参数类型就是那个外围类。这个构造器只从它的参数中复制数据:它不需要进行任何一致性检查或保护性拷贝。从设计的角度看,序列化代理的默认序列化形式是外围类最好的序列化形式

用writeReplace添加到外围类。通过序列化代理,代替外围类的实例。为了防止攻击者伪造,只要在外围类中添加readObject方法抛出异常。最后,嵌套类中提供一个readResolve方法,它返回一个逻辑上相当于外围类的实例。

序列化代理模式有两个局限性。它不能与可以被客户端扩展的类兼容。它不能与对象图中包含循环的某些类兼容:如果你企图从一个对象的序列化代理的readResolve方法内部调用这个对象中的方法,就会得到一个ClassCastException异常,因为你还没有这个对象,只有他的序列化代理。

最后,序列化代理模式所增强的功能和安全性并不是没有代价的。

总而言之,每当你发现自己必须在一个不能被客户端扩展的类上编写readObject或者writeObject时,就应该考虑使用序列化代理模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,863评论 0 24
  • 本章关注对象序列化API,它提供了一个框架,用来将对象编码成字节流,并从字节流编码中重新构建对象。 相反的处理过程...
    Timorous阅读 252评论 0 1
  • 正如前文《Java序列化心得(一):序列化设计和默认序列化格式的问题》中所提到的,默认序列化方法存在各种各样的问题...
    登高且赋阅读 8,389评论 0 19
  • 对象序列化:将一个对象编码成字节流。反之,成为对象反序列化。 第74条:谨慎地实现Serializable接口 实...
    wangcanfeng阅读 464评论 0 1
  • 官方文档理解 要使类的成员变量可以序列化和反序列化,必须实现Serializable接口。任何可序列化类的子类都是...
    狮_子歌歌阅读 2,409评论 1 3