一、序列化与二进制串
序列化其实就是将数据结构转换成二进制串的过程。这里的二进制串,在 Java 中很容易和 String 的概念混淆,实际上 String 也是一种特殊对象(Object),对于跨语言间的通信,序列化后的数据当然不能使某种语言的特殊数据类型。
二进制串在 Java 里面所指的应当是 byte[]
二、序列化/反序列化的目的
数据的生命周期需要比 JVM 长。Java 允许我们在内存中创建可复用的对象,但是一般只有 JVM 存在的时候,这些对象才能存在,即对象生命周期不能比 JVM 更长。所以,如果需要停止 JVM 后数据还存在,就要序列化保存这些数据。
序列化只是对变量而言。序列化对象的时候,只针对变量进行序列化,不针对方法进行序列化。
需要永久保存或者在网络上传输的时候,需要序列化之后才能进行。
三、序列化常见的方案
Java 中有 Serializable
Android 中有 Parcelable
还有 Json、xml 、Protocol Buffer 等
四、选择合理的序列化方案
性能
通用性
鲁棒性
可调式性/可读性
可扩展/兼容性
安全性/访问限制
五、Serializable
首先, Serializable 只是一个接口,为什么一个空的接口能够实现序列化?因为它只是一个标识、标记!
如果要保存到磁盘上,还需要使用 IO 流(ObjectInputStream/ObjectOutPutStream)来辅助,这个好理解。
六、Externalizable
要自己实现 writeExternal 和 readExternal 方法,但是使用的时候需要注意以下几点:
write 和 read 方法二者要对应,比如,write 的阶段没有只写入了一个 String 类型,但是读的时候 读了 String 还要读 int ,这时候就会导致崩溃报错。
write 和 read 的顺序要保持一致,比如先 write 了String ,再 write 了int ,读的时候如果先读int ,后读String ,也会导致报错
要有默认无参的、public类型的构造函数,如果你自己写了有参数的构造函数,一定再加上无参的默认构造函数
为什么要有一个 public 无参的构造函数?
七、相关面试题
7.1 什么是 serialVersionUID ? 如果不定义会发生什么?
serialVersionUID 是一个 private static final long 类型的 ID ,它通常是对象的 hashCode
它用于对象的版本控制,我们可以在类文件中指定这个值
如果不指定它的值,当你序列化类 A.java 生成了文件 a.txt ,之后,在 A 类中添加了一个字段 sex ,则在反序列化的时候会报错,提示版本不一致
还有,如果指定serialVersionUID 为 1,序列化 A 的时候生成了 a.txt ,这时候添加了字段 sex ,如果此时不改 serialVersionUID 的值,反序列化的时候,能成功,只是 sex 字段为 null(这里说的是对象类型,如果是 int 等类型就会是 0 ,后面一样)而已
接着上面,如果序列化了 A ,添加了 sex 字段,此时还将 serialVersionUID 改为 2 ,此时反序列化就会报错,同样是版本不一致
7.2 序列化的时候,如果某些成员不要序列化,该怎么实现?
使用 transient 关键字。加了 这个关键字的对象 sex ,在反序列化的时候会为null(如果是int 等类型就会是 0) 。
7.3 如果类 A 中有成员未实现可序列化接口,会发生什么?
如果 A 中有成员 User 对象没有实现可序列化接口,序列化的时候会报 “不可序列化”的异常。
7.4 如果类 A 是可序列化的,但是其父类不是,则反序列化后,从父类继承过来的实例变量状态如何?
假如 User 继承了 Person 类,并且实现了 Serializable 接口,但是 Person 类没有实现 Serializable 接口。
即 父类的成员未默认值,Object 类型为 null ,int 等类型为 0。
如果要想父类也实现,需要父类也实现 Serializable 接口,并且有默认的构造函数(无参的,public 的)。