Java末日第1篇

问:谈谈你对 Java 序列化与反序列化理解?

答:序列化就是将对象转化为字节流,反序列化就是将字节流转化为对象,默认的序列化是深度系列化(即类中包含的深度嵌套其他对象引用的对象都会被序列化),静态成员不会被默认序列化,要让一个类支持序列化只要让这个类实现接口 java.io.Serializable 即可,Serializable 只是一个没有定义任何方法的标记接口,声明实现 Serializable 接口后保存读取对象就可以使用 ObjectOutputStream、ObjectInputStream 流了,ObjectOutputStream 是 OutputStream 的子类,但实现了 ObjectOutput 接口,ObjectOutput 是 DataOutput 的子接口,增加了一个 writeObject(Object obj) 方法将对象转化为字节写到流中,ObjectInputStream 是 InputStream 的子类,实现了ObjectInput 接口,ObjectInput 是 DataInput 的子接口,增加了一个 readObject() 方法从流中读取字节转为对象,序列化和反序列化的实质在于 ObjectOutputStream 的 writeObject 和 ObjectInputStream 的 readObject 方法实现,常见的 String、Date、Double、ArrayList、LinkedList、HashMap、TreeMap 等都默认实现了 Serializable。

有时候我们对象有些字段的值可能与内存位置(hashcode)、当前时间等有关,所以我们不想序列化他(因为反序列化后的值是没有意义的),或者有时候如果类中的字段表示的是类的实现细节而非逻辑信息则默认序列化也是不适合的,所以我们需要定制序列化,Java 提供的定制主要有 transient 关键字方式和实现 writeObject、readObject 方式及 Externalizable 接口 readResolve、writeReplace 方式,还可以将字段声明为 transient 后通过 writeObject、readObject 方法来自己保存该字段。

默认情况下 Java 会根据类中一系列信息自动生成一个版本号,在反序列化时如果类的定义发生了变化版本号就会变化,也就与反序列化流中的版本号不匹配导致会抛出异常,所以我们为了更好的控制和性能问题会自定义 serialVersionUID 版本号来避免类定义发生变化后反序列化版本号不匹配异常问题,如果版本号一样时流中有该字段而类定义中没有则该字段会被忽略,如果类定义中有而流中没有则该字段会被设为默认值,如果对于同名的字段类型变了则会抛出 InvalidClassException。

虚拟机是否允许反序列化不仅取决于类路径和功能代码是否一致,还取决于另一个非常重要的点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。

推荐阅读更多精彩内容