1. 序列化和反序列化
首先来解释一下什么是序列化和反序列化:
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
- 在 Java 和其他语言进行通信的时候,需要将对象转化成一种通用的格式例如Json( 转换成大家都认识的对象 ),从对象到 Json 字符串的转换过程就是序列化的过程,反过来, 从 Json 字符串转换成 Java 对象的过程就是反序列化的过程。
当 Java 需要把一个对象的状态保存到文件或者是数据库的时候,就是数据存取的过程中的中间过程,就需要序列化。我们可以把序列化的过程理解为 “freeze”,它将一个 Java 对象 freeze,然后进行存储;等到再次需要的时候,再将这个对象 “de-freeze” 就能使用了。
2. 为什么要 implements Serialzable?
一个类只有实现了 Serializable 接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现 Serializable 接口。而实际上,Serializable 的源码是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
- serialization 允许你将实现了 Serializable 接口的对象转换为字节序列,这些字节序列可以被完全存储以备以后重新生成原来的对象。
3. 那么什么情况下需要实现序列化?
- 把内存中的对象写入到硬盘。例如,你的内存不够用了,那计算机就要将内存里面的一部分对象暂时的保存到硬盘中,等到要用的时候再读入到内存中,硬盘的那部分存储空间就是所谓的虚拟内存。再例如,你要将某个特定的对象保存到文件中,隔几天在把它拿出来用,那么这时候就需要实现 Serializable 接口。
- 使用套接字在网络上传送对象,通常情况下你可以在 server 端用获取 socket的 outputstream inputstream,将取得的数据处理成字符串发送到客户端,然后在客户端拆分字符串,但是这样显然会降低效率,于是你可以将 server 端数据 包装 成一个 class implements Serializable,然后直接用objectoutputstream,objectinputstream 直接传递。
- 通过 Java的 RIM(remote method invocation)传输对象。RMI允许对象在本机上可以一样操作远程机器上的对象。当发送消息给远程对象时,就需要用到serializaiton 机制来发送参数和接收返回值。
实体类实现序列化的目的:
- 一是便于存储
- 二是便于传输
像 boolean 、int、long 类型等,都是基本数据类型,数据库里面有与之对应的数据结构。从类声明来看,我们以为的没有进行序列化,其实是在声明各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。所以就算我们不实现 serializable 依旧可以正常操作。
如果平时留意的话,我们会发现序列化操作用于存储时,一般是对于 NoSql 数据库,而在使用 Nosql 数据库进行存储时,如 redis,它就没有 varchar,int 之类的数据结构。 而在没有的情况下,我们又确实需要进行存储,那么我们就需要将对象进行序列化。
4. 为什么要显示声明 serialVersionUID
serialVersionUID 的作用是验证序列化和反序列化的过程中,对象是否保持一致。所以在一般情况下我们需要显示的声明serialVersionUID。如果接受者加载的该对象的类的 serialVersionUID 和发送者的类版本号不同的话,反序列化会爆出InvalidClassException 错误。
什么时候会导致报出这个错误呢?例如在没有显示声明版本号的时候,先将对象进行了序列化;然后不管出于什么目的,该对象的类被修改了,哪怕仅仅是添加了一个强制转换,或者将一个 public 的属性给私有化了,都会影响版本号。此时在这个环境下用反序列化的方法读取以前序列化之后存储起来的对象是会报错的。
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@TableId("id")
private Integer id;
@TableField("name")
private String name;
@TableField("age")
private Integer age;
}