IPC机制系列四 Serializable接口

Serializable接口

       Serializable 是 Java 所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。

1
private static final long serialVersionUID = 8711368828010083044L;

       在Android中也提供了新的序列化方式,那就是Parcelable接口,使用它来实现对象的序列化,其过程要稍微复杂一点,先介绍Serializable接口。上面提到,想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实际上,甚至这个serialVersionUID也不是必须的,我们不声明这个serialVersionUID同样也可以实现序列化,但是这将会对反序列化过程产生影响。User类就是一个实现了Seralizable接口的类,它是可以被序列化和反序列化的:

1
2
3
4
5
6
7
8
9
10
public class User implements Serializable {

private static final long serialVersionUID = 8711368828010083044L;

public int userId;
public String userName;
public boolean isMale;

...
}

       通过Serializable方式来实现对象的序列化,实现起来非常的简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常的简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现,下面举一个简单的例子。

1
2
3
4
5
6
7
8
9
10
//序列化
User user = new User(0, “Jay", true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();

       上述代码演示了采用Serializable方式序列化对象的典型过程,只需要把实现了 Serializable接口的 User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。

       serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常地被反序列化。serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其它中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。

       一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让开发工具根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash。所以,我们可以明显感觉到serialVersionUID的作用,当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量或者增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度恢复数据,相反,如果不指定serialVersionUID的话,程序则会挂掉。当然我们还要考虑另外一种情况,如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

       根据上面的分析,我们可以知道,给serialVersionUID指定为1L或者采用开发工具根据当前类结构去生成的hash值,这两者并没有本质区别,效果完全一样。以下两点需要特别提一下,首先静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。

       另外,系统的默认序列化过程也是可以改变的,通过实现如下两个方法即可重写系统默认的序列化和反序列化过程。不过大部分情况下我们不需要重写这两个方法。

1
2
3
4
5
6
7
8
private void writeObject(ObjectOutputStream out) throws IOException {  
// write “this” to “out”…
}

private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
// populate the fields of “this” from the data in “in”
}

参考资料:
《Android 开发艺术探索》任玉刚 第2章 2.3.1 Serializable接口

Fork me on GitHub