0x01 概述
什么是序列化,简单的来说,序列化就是为了保存对象的状态;而反序列化就是把保存的对象状态再读出来。
使用场景:
- 当想把内存中的对象状态保存到一个文件或者数据库中的时候
- 当想用套接字在网络上传送对象的时候
- 当想通过RMI传输对象的时候
0x02 Java支持序列化种类
Java支持的序列化有三种
- 自定义实现Serializable接口的类
- Java的基本类型
- Java自带的实现了Serializable接口的类
下面用程序展示着三种情况
支持自定义实现Serializable接口的类:
1 | import java.io.FileInputStream; |
运行结果:
支持java的基本类型和自带的实现了Serializable接口的类
1 | import java.io.FileInputStream; |
运行结果
这里HashMap是java.util包中定义的类,它属于java自带的实现Serializable接口的类,它的接口声明如下:
1 | public class HashMap<K,V> extends AbstractMap<K,V> |
0x03 序列化中的特例
从上面说得,我们知道序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法,但是,序列化是不是对类的所有的成员变量的状态都能保存呢?答案是否定的。
- 序列化对static和transient变量,是不会自动进行状态保存的。
transient的作用就是,用transient声明的变量,不会被自动序列化。
- 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!
static与transient
首先说一下序列化对static和transient的处理吧,我们将之前的代码中Box类修改一下
1 | class Box implements Serializable { |
将成员变量的类型修改为static和transient,运行一下,结果:
前面说得,序列化不对static和transient变量进行状态保存的。因此,testWrite()中保存Box对象时,不 会保存width和height的值。但是为什么testRead()读出来的Box对象中width=80,而height=0呢?
对于height,因为Box对象中height是int类型,而int类型默认是0,因此height为0.
而对于width,它是static类型,而static类型意味着所有Box对象都公用一个heith值,而在testWrite()中,我们已经将其初始化为80,因此,我们通过序列化读出来width也是80.
那么,如果我们想要保存static或transient变量,也是可以的,只要重写两个方法writeObject()和readObject()即可。
还是Box类
1 | class Box implements Serializable { |
在writeObject()方法中,out.defaultWriteObject()是使定制的writeObject()方法可以利用自动序列化中内置的逻辑
在readObject()方法中,in.defaultReadObject()也是使定制的readObject()方法可以利用自动序列化中内置的逻辑。
Socket、Thread类
在Box类中添加
1 | private Thread thread = new Thread() { |
运行发现,直接编译报错!
事实证明,不能对Thread进行序列化,若希望程序能便宜通过,我们对Thread变量添加static或transient修饰符即可。
0x04 完全定制序列化过程Externalizable
如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。
Externalizable接口定义包括两个方法writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。
实例:
1 | import java.io.FileInputStream; |