收藏了!70%的程序员都不知道的序列化细节,清晰明了

1.谨慎实现serializable接口问题
序列化过程是将“一个对象编码成一个字节流” , 相反的处理过程被称之为“反序列化过程” 。 当一个对象被序列化后 , 它的编码就可以从一台虚拟机传至另一个台虚拟机 , 可以被保存在磁盘上 , 方便以后反序列化使用 。 长期以来有一个误解 , 为了实现序列化 , 只需要实现Serializable接口即可 , 事实上这种方式存在诸多危害 , 贪图这种序列化方式的方便 , 会带来长期维护的成本 。 关于Serilizable有哪些注意事项?
答案
Serializable的缺点
直接实现Serializable接口有如下这些缺点:
降低灵活性:如果一个类实现了Serializable接口 , 它的字节流编码也变成了它导出API的一部分 , 一旦这个类被广泛使用 , 就必须永远支持这种序列化方法 。 并且 , 如果使用了默认的Serializable , **这个类中私有的和包级私有的实例域都会变成导出的API的一部分 , 这不符合域最小访问级别的设计原则 。 **另外 , 如果改变了类的内部结构的话 , 客户端企图用类的旧版本来序列化 , 而使用新版本来进行反序列化的话 , 程序就会出错 。如果被序列化的类没有显示的指定serialVersionUID标识(序列版本UID) , 系统会自动根据这个类来调用一个复杂的运算过程生成该标识 。 此标识是根据类名称、接口名称、所有公有和受保护的成员名称生成的一个标志号 。 如果改变了类的内部结构 , 如添加了一个方法 , 自动产生的序列版本UID也会发生变化 。 因此 , 如果没有显式的声明一个版本号的话 , 兼容性就会遭到破坏 , 在运行时导致InvalidClassException 。
更容易引发Bug和安全漏洞:
一般对象是由构造器创建的 , 而序列化也是一种对象创建机制 , 反序列化也可以构造对象 。 由于反序列化机制中没有显式的构造器 ,
反序列化要确保:
由真正的构造器建立的约束关系 , 并且不允许攻击者访问正在构造过程中的对象的内部信息 。 依靠默认的反序列化机制 , 很容易使对象的约束关系遭到破坏 , 以及遭受到非法访问 。相关测试负担加重:当一个可序列化的类被修改后 , 需要检查“在新版中序列化一个实例 , 在旧版本中反序列化”及“在旧版本中序列化一个实例 , 在新版本反序列化”是否正常 , 当发布版本增多时 , 这种测试量与“可序列化的类的数量和发行版本号”的乘积成正比 。2.Serializable适用场景 若一个类要加入某个框架 , 而该框架是依赖序列化来实现对象的传输和持久化 , 那么该类实现Seriablizable就是有必要 , 更进一步来看 , 一个类属于一个组件 , 如果父组件实现了Seriablizable接口 , 那么该类也需要实现Seriablizable接口 。 根据经验 , 比如Date和BigInteger这样的值类应该实现Serializable , 大多数集合类也需要实现 。3.Serializable不适用场景 为了继承而设计的类应该尽可能少地去实现Serializable接口 , 用户接口也应该尽可能不继承Serializable接口 , 原因是子类或实现类也要承担序列化的风险 。 大多数情况下需要遵守这条原则 , 极为特殊的情况可以打破这项原则 , 比如实现Serializable接口的的类有Throwable类(异常可以从服务器端传到客户端)、Component类(GUI可以被发送、保存和恢复)、HttpServlet抽象类(会话session可以被缓存);内部类不应该实现Serializable , 内部类需要保存指向外部类实例的引用及保存来自外部作用域的局部变量的值 。 这些域如何对应到类定义中不确定 。 因此内部类的默认序列化形式定义不清楚 。
结论
总之 , 千万不要将序列化就等同于简单的实现Serilizable接口 , 应该要考虑着重考虑Seriablizable的应用场景和上面所述的这些注意事项 。
考虑使用自定义序列化形式问题
设计一个类的序列化形式和设计该类的API 同样重要 , 因此在没有认真考虑好默认的序列化形式是否合适之前 , 不要贸然使用默认的序列化行为 。 在作出决定之前 , 你需要从灵活性、性能和正确性多个角度对这种编码形式进行考察 。 一般来讲 , 只有当你自行设计的自定义序列化形式与默认的形式基本相同时 , 才能接受默认的序列化形式 。 选择合适的序列化方式 , 有哪些注意事项?


推荐阅读