什么是序列化
当两个进程远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息,我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送,接收方则需要把字节序列再恢复为Java对象才能正常读取。
- 序列化: 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。
- 反序列化: 客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
为什么要序列化
- 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。
- 网络通信: 在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
怎样来序列化
在Java中,想实现序列化,有两种方法:
实现Serializable
接口
一个对象想要被序列化,那么它的类就要实现此接口或者它的子接口。这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。不想序列化的字段可以使用transient
修饰。由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必须有默认构造函数,否则会抛异常。使用transient
关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()
方法与readObject()
方法可以控制如何序列化各属性,甚至完全不序列化某些属性或者加密序列化某些属性。
实现Externalizable
接口
它是Serializable接口的子类,用户要实现的writeExternal()
和readExternal()
方法,用来决定如何序列化和反序列化。因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,而transient
在这里无效。对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor
异常,因此Externalizable对象必须有默认构造函数,而且必须是public的。
序列化版本:
序列化过程中可以控制序列化的版本,该字段为被序列化对象中的serialVersionUID字段。
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
}
反序列化时会讲序列化串中的serialVersionUID与当前对象中的序列化版本比较,如果一致才能成功反序列化。
如果没有显式的声明serialVersionUID,系统会自动生成一个,类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器中任何一样发生变化,都会导致serialVersionUID改变。所以还是显式的声明比较好。