1.序列化的含义和作用
序列化用来将对象编码成字节流,反序列化就使将字节流编码重新构建对象。
序列化实现了对象传输和对象持久化,所以它能够为远程通信提供对象表示法,为JavaBean组件提供持久化数据。
2.序列化的危害
1.降低灵活性:为实现Serializable而付出的最大代价是,一旦一个类被发布,就大大降低了”改变这个类的实现”的灵活性。如果一个类实现了Serializable,它的字节流编码(或者说序列化形式,serialized form)就变成了它的导出的API的一部分,必须永远支持这种序列化形式。
而且,特殊地,每个可序列化类都有唯一的标志(serial version id,在类体现为私有静态final的long域serialVersionUID),如果没有显式指示,那么系统就会自动生成一个serialVersionUID,如果下一个版本改变了这个类,那么系统就会重新自动生成一个serialVersionUID。因此如果没有声明显式的uid,会破坏版本之间的兼容性,运行时产生InvalidClassException。
2.降低封装性:如果你接受了默认的序列化形式,这个类中私有的和包级私有的实例域将都变成导出的API的一部分,这不符合”最低限度地访问域”的实践准则。
3.降低安全性:增加了bug和漏洞的可能性,反序列化的过程其实类似于调用对象的构造器,但是这个过程又没有用到构造器,因此如果字节流被无意修改或被用心不测的人修改,那么服务器很可能会产生错误或者遭到攻击。
16年出现的大名鼎鼎的Java反序列化漏洞本质上就是不恰当的序列化造成的。
4.降低可测试性:随着类版本的不断更替,必须满足版本兼容问题,所以发行的版本越多,测试的难度就越大。
5.降低性能:序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其他对象也进行序列化。如果一个对象包含的成员变量是容器类等并深层引用时(对象是链表形式),此时序列化开销会很大,这时必须要采用其他一些手段处理。
3.序列化的使用场景
1.需要实现一个类的对象传输或者持久化。
2.A是B的组件,当B需要序列化时,A也实现序列化会更容易让B使用。
4.序列化不适合场景
为了继承而设计的类应该尽可能少地去实现Serializable接口,用户接口也应该尽可能不继承Serializable接口,原因是子类或实现类也要承担序列化的风险。
5.序列化需要注意的地方
1)如果父类实现了Serializable,子类自动序列化了,不需要实现Serializable;
2)若父类未实现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();//再反序列化父类的域
}
3)序列化时,只对对象状态进行了保存,对象方法和类变量等并没有保存,因此序列化并不保存静态变量值。
4)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象序列化了。所以组件也应该序列化。
5)不是所有对象都可以序列化,基于安全和资源方面考虑,如Socket/thread若可序列化,进行传输或保存,无法对他们重新分配资源。