谨慎的使用 Serializable 接口(74)

拜拜、爱过 提交于 2019-12-20 10:30:03

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

  • 序列化带来的直接开销非常低,但是长期开销是实实在在的

实现 Serializable 接口最大的代价就是

  • 一旦一个类发布,大大降低了改变其实现的灵活性
  • 因为此时,它的字节流编码就变成了其导出API的一部分
  • 如果不设计一种自定义序列化形式,仅仅使用默认序列化,
    • 那么私有和包级私有实例,都变成导出api的一部分
    • 不符合最低限度访问域的准则
    • 默认序列化可能出现新老版本序列化和反序列化不兼容
      • 仍然保留老接口,会带来别的问题、隐患
    • 仔细设计一种高质量的序列化形式,长期使用,初始付出的成本是值得的
  • 序列化会使得类的演变受到限制
    • 流的唯一标识符有关(序列版本UID)
      • private static final long serialVersionUID = 1L;
      • 如果不定义该标识符,会在运行时,调用一个复杂的过程自动生成
        • 而且内部改变,会导致,自动计算id 值改变,序列化兼容被打破
          • 导致InvalidClassException

实现 Serializable 第二个代价:

  • 增加了出现 BUG、安全漏洞的可能性
    • 对象是构造器创建的
    • 序列化机制是语言之外的对象创建机制
      • 反序列化机制都是一个隐藏的构造器
        • 该构造器相对于真正的构造器,约束条件往往被忽略
      • 默认序列化机制的反序列化过程的约束关系很容易遭到破坏、非法访问

第三个代价:

  • 随着类发行新的版本、相关测试负担也增加了
    • 一个可序列化的类被修订后,要检查,在新版本序列化一个类,在老版本是否可以反序列化,反之亦然
    • 测试工作量乘积增长

实现Serializable 确实带来了益处:

  • 比如一些值类:Date、BigInteger 可以实现Serializable
  • 活动实体类:Thread pool 一般不实现Serializable

为了继承而设计的类、接口,尽可能少的实现 Serializable 接口

  • 但是如果专门设计参与到某个框架的类,该框架要求必须实现Serializable 时例外
  • 为了继承而设计的类,实现了Serializable 接口的有
    • Throwable类:RMI异常,可以从服务端传到客户端
    • Component :GUI 可以被发送保存和恢复
    •  HttpServlet抽象类:会话状态可以被缓存
  • 如果实现带有实例域的类,实例域被初始化成默认值会违背约束条件
    • 就必须添加下文中的方法

如果一个专门为了继承而设计的类不是可序列化的,

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

内部类不应该实现Serializable。

  • 它们使用编译器产生的合成域来保存指向外围实例的引用,
  • 以及保存来自外围作用域的局部变量的值。
  • 因此,内部类的默认序列化形式是定义不清楚的。
  • 然而,静态成员类却是可以实现Serializable接口。

千万不要认为实现Serializable接口会很容易。

  • 除非一个类在用了一段时间之后就会被抛弃,
    • 否则,实现Serializable接口就是个很严肃的承诺,必须认真对待。
  • 如果一个类是为了继承而设计的,则更加需要加倍小心。
    • 对于这样的类而言,在“允许子类实现Serializable接口”或“禁止子类实现Serializable接口”两者之间的一个折衷设计方案是,
      • 提供一个可访问的无参构造器,这种设计方案允许(但不要求)子类实现Serializable接口。
      • 至于为什么需要父类有一个无参的构造器,
        • 是因为子类先序列化自身的时候先调用父类的无参的构造器。 
        • 实例:
          • private void writeObject(java.io.ObjectOutputStream out) 
              throws IOException{ 
               out.defaultWriteObject();//先序列化对象 
               out.writeInt(parentvalue);//再序列化父类的域 
              } 
              private void readObject(java.io.ObjectInputStream in) 
              throws IOException, ClassNotFoundException{ 
               in.defaultReadObject();//先反序列化对象 
                 parentvalue=in.readInt();//再反序列化父类的域 
              } 
            

             

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!