详解Java的序列化机制

      Java的序列号机制允许将对象转换成与平台无关的二进制流,从而实现对象保存到磁盘、在网络中传输等。Java中通过实现Serializable接口,标识对象序列化。查看源码可发现,Serializable接口不包含任何方法和域,只是起到标识作用:


图1: Serializable接口

一、序列化原理

1、每个序列号的对象都是采用了一个序列号进行保存

2、当序列化一个对象时,程序将检查该对象是否已经序列化过:

      ---该对象若未进行序列化,则采用流中数据来构建它,并为该对象关联一个序列号;

      --​-​若该对象已经序列化过,则程序直接输出该对象关联的序列号,通过该序列号可获得该对象引用。


二、自定义序列化

​自定义序列化一般通过重写readObject(...)、writeObject(...)和readObjectNoData(...)方法实现

图2: 自定义序列化

三、版本管理

1、在进行对象序列化时,系统会默认给每个对象生成一个指纹,即序列化版本ID(serialVersionUID)​。系统默认生成的UID是根据对象的类、超类、接口、域和方法等信息计算得到,如果对象信息发生变化,该UID也会发生变化。

​2、在读入一个对象时,会将该对象的UID与其所属类的UID进行对比,如果两者不同,则说明这个类的定义在该对象被写出后发生了变化(版本不同),进而产生一个异常。

3、​通过在可序列化类中定义一个public static final long serialVersionUID,实现版本兼容:

图3: 添加版本号,实现版本兼容

​4、如果版本不同(UID不同):

      ---在读入流中对象时,如果流中对象具有当前版本所没有的数据域,那么对象流会忽略这些额外的数据;如果当前版本具有在流中对象所没有的数据域,那么当前版本中新增加的数据域会被设置成默认值。


四、readResolve()方法

1、如果定义了read​Resolve()方法,那么该方法会在对象序列化之后被调用,该方法返回的对象会成为readObject()方法的返回值!该方法拥有private、protected和包私有等访问权限。

2、在对单例模式等进行实例控制的类进行序列化时​,由于readObject()会重新创建一个不同的对象,而导致实例数量控制失败,可通过readObject()方法解决:

图4: 测试用例

      ---注意:在图4中,instance申明为transient。

图5: 测试用例
图6: 注释掉readResolve()结果

​      ---由图6结果可知,在未添加readResolve()方法时,每次反序列化都重新创建了一个对象,导致单例控制失败;

图7: 添加readResolve()结果

      ---由图7结果可知,在readResolve()中直接返回实例,忽略了反序列化后的对象,确保反序列化后,仍然只存在一个实例。

      ---由于在readResolve()中忽略了反序列化后的对象,因此如果通过readResolve()进行实例控制,那类的所有实例域都应该申明为transient !​


五、writeReplace()方法​

1、writeReplace()是一种更彻底的自定义机制,它可以在序列化对象时,将序列化的对象替换成其它对象,该方法可拥有private、protected和包私有等访问权限。

2、​系统在序列化对象时,先调用该对象的writeReplace()方法,如果该方法返回另一个对象,则系统转为序列化另一个对象:即再次调用另一个对象的writeReplace()方法……直到不再返回另一个对象为止。

图8: 序列化成List对象
图9: 测试用例
图10: 测试结果

由图10可知,序列化Person对象时,转而序列化成了List对象。


六、另一种自定义序列化机制(Externalizable)

​1、Externalizable序列方式完全由程序员决定存储和恢复对象数据。

2、Externalizable接口与Serializable接口非常相似,只是Externalizable接口强制程序员自定义序列化。

3、通过重写readExternal(...)和writeExternal(...)实现反序列化和序列化。

4、注意:实现Externalizable​接口的类必须提供一个无参构造函数:在反序列化对象时,对象流将调用无参构造函数创建一个对象,在调用readExternal(...)方法!


七、总结​

1、对象的类名、域(基本类型、数组、对象引用)都会被序列化,但方法、static域和transient域不会被初始化!​

2、如果超类没有提供一个可访问的无参构造函数,子类是不能看实现序列化的。

3、反序列化是一个“隐藏的构造器”,默认会构造一个新的对象。

4、内部类的默认序列化形式是定义不清楚的,因此内部类不该实现Serializable;但静态成员类却可以实现该接口。

5、根据经验:比如Date和BigInteger这样的值类应该实现Serializable接口,大多数的集合类也该如此;代表活动实体的类,如线程池,一般不该实现该接口。

6、为了继承设计的类和用户接口,应尽可能的少实现Serializable接口。​

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,923评论 0 24
  • 正如前文《Java序列化心得(一):序列化设计和默认序列化格式的问题》中所提到的,默认序列化方法存在各种各样的问题...
    登高且赋阅读 8,489评论 0 19
  • 将一个对象编码成字节流称作将该对象「序列化」。相反,从字节流编码中重新构建对象被称作「反序列化」。一旦对象被「序列...
    Alent阅读 794评论 0 1
  • 对象序列化:将一个对象编码成字节流。反之,成为对象反序列化。 第74条:谨慎地实现Serializable接口 实...
    wangcanfeng阅读 479评论 0 1
  • 121,121 bingo!这可不是什么军训口号,这是我们宿舍的号码牌,在我们骄傲地向班级里其它开始熟络的同学打...
    yao春阅读 373评论 0 1