1. 原理
之前有一篇文章 transient关键字 ,是讲基于 Serializable 的序列化,这篇文章主要讲基于 Externalizable 的序列化。如果类实现 Serializable ,会自动序列化,但是如果实现 Externalizable ,需要我们自己手动序列化。使用很简单,只需要实现 Externalizable 接口,重写其中的 writeExternal 、readExternal 两个方法即可。看看 Externalizable 接口的源码。
1 | /** |
接口有两个方法,writeExternal 序列化,readExternal 反序列化。
翻译一下 writeExternal 的注释:
一个类通过实现 writeExternal 方法来保存它的内容(即序列化)。如果是基本数据类型,调用 DataOutput 的方法(有兴趣的同学可以看一下 DataOutput 接口中定义了各种基本类型的 write 方法);如果是对象、字符串、数组,则调用 ObjectOutput 的 writeObject 方法。
翻译一下 readExternal 的注释:
一个类通过实现 readExternal 方法来恢复它的内容(即反序列化)。如果是基本数据类型,调用 DataInput 的方法;如果是对象、字符串、数组,则调用 readObject 方法。这里的注释没有写清楚 readObject 是哪个类的方法,应该是 ObjectInput 的 readObject 方法。readExternal 方法读取数据时,顺序和类型必须与 writeExternal 方法保持一致(这一句很重要)。
2. 入门测试
writeExternal 和 readExternal 方法底层是调用了什么方法去进行序列化以及反序列化并不是这篇文章所关心的。我们只需要知道这两个方法能帮我们实现序列化/反序列化就行。下面写个类测试下:
1 | package com.hema.carloan; |
如上述代码所示,反序列化的时候,将 password 的序列化注释掉。下面写个单元测试:
1 |
|
执行结果如下:
读取磁盘的对象:User{name=’zhaoxiaofa’, password=’null‘, mobile=’13812345678’}
结果很容易想出来,因为在 readExternal 方法中并没有加上 password 。所以反序列化之后为 null 。
readExternal 方法的注释上有一句,readExternal 读取数据时,顺序和类型必须与 writeExternal 方法保持一致。为什么要强调顺序?如果顺序不一致会怎样?下面来测试一下,修改 readExternal 的方法,只反序列化 mobile 。代码如下:
1 |
|
执行结果:
读取磁盘的对象:User{name=’null’, password=’null’, mobile=’zhaoxiaofa’}
从结果中我们看到,在反序列化时将 name 的值赋值给了 mobile 。name 是第一个被序列化的变量,mobile 是第一个也是唯一一个被反序列化的变量,所以把 name 的值赋值给了 mobile 。这就是 readExternal 强调的,顺序要保持一致。
3. 一定要有默认的无参构造函数
在 ExternalizableUser 类中加入如下构造方法,再执行序列化和反序列化,最后执行单元测试会报错。
1 | public ExternalizableUser(String name, String password) { |
java.io.InvalidClassException: no valid constructor
小结:
必须有权限为 public 的默认的构造器(如果有非默认的带参数的构造函数,那么必须显示的写出默认的构造函数),这样 Externalizable 才能正确执行序列化和反序列化。