Java的序列化提供了一种保存对象状态的机制,之所以要用到序列化是因为有时我们会在对象的生命期结束后需要把对象状态保存下来,通常是存放到外部的媒介上,比如文件,磁盘或网络上。并且在需要时能够通过一种机制来恢复。序列化能够在不同的JVM之间共享数据。
实现类的序列化在语法上非常简单,只需要实现Serializable接口即可。Serializable接口没有需要实现的方法(这是一个”标记接口-tagging interface”),只是标明该类的对象需要序列化。
通常我们在构建javabean的时候,定义Bean时要实现这个接口,因为Bean的属性状态一般都是需要保存的。
关于序列化
需要注意以下几点:
- 序列化保存的是对象的状态。状态包括对象的非静态成员变量(包括声明为private的变量),不能保存任何的成员方法和静态的成员变量。
- 当一个父类实现序列化时,子类自动序列化,不需要显式实现Serializable接口。
- 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象序列化。
- 序列化的对象包括基本数据类型,所有集合类以及其他许多东西,还有对象。但是并非所有的对象都可以序列化。
- 对象序列化不仅保存了对象的状态,而且还能保存对象内包含的所有引用的对象状态,一直追踪知道所有的引用的对象被保存。
- 使用transient关键字修饰的的变量,在序列化对象的过程中,该属性不会被序列化。常用于当某些变量不想被序列化,同是又不适合使用static关键字声明时。
关于 serialVersionUID
序列化运行时使用一个称为serialVersionUID的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的serialVersionUID与对应的发送者的类的版本号不同,则反序列化将会导致InvalidClassException。可序列化类可以通过声明名为 serialVersionUID
的字段(该字段必须定义为 static final long)显式声明其自己的serialVersionUID.建议在一个可序列化类中显示的定义serialVersionUID。因为serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。而且不同的Java编译器之间会有差异。
可以用在如下的场合:
- 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
- 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID
参考代码如下,改代码演示了如何序列化和反序列化一个对象:
首先定义一个类实现Serializable接口。
DemoInstance.java
在main()中实现该类对象的序列化和反序列化。
App.java
以上代码实现了对象的序列化,注意此时如果将保存的文件用文本编辑器打开会看到乱码。
这是因为序列化和反序列化都是基于二进制流的,将对象保存的信息转化为二进制存储在了文件中,那么用文本编辑器打开查看的话当然是会出现乱码的。只有通过反序列化才能将存储的二进制读取出来。
反序列化读取的代码如下。
结果如下所示。
以上是序列化的基本步骤,下面我们再定义一个DemoInstance的子类,该类没有直接实现Serializable接口,同时在该类中还引用了另外一个没有实现Serializable接口的实体类。
SubDemoInstance.java
ClassWithoutSerial.java
实现序列化和发序列化的代码。
执行后会出现如下错误。
可以看到错误为ClassWithoutSerial没有序列化却没执行了序列化的操作。稍作修改使得类ClassWithoutSerial实现Serializable接口,结果如下。
由此可见,序列化类的子类可以不必实现序列化,但是其内部引用的类对象必须实现序列化。