15.8.2 使用对象流实现序列化

15.8.2 使用对象流实现序列化

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。关于这两个接口的区别和联系,后面将有更详细的介绍,读者先不去理会Externalizable接口。
使用Serializable来实现序列化非常简单,主要让目标类实现Serializable标记接口即可,无须实现任何方法.

序列化对象步骤

一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象。

1. 创建objectOutputStream

创建一个ObjectorOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上。如下代码所示。

1
2
3
// 创建个 Objectoutputstream输出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("object.txt"));

2. 调用writeObject方法

调用ObjectOutputStream对象的writeObject方法输出可序列化对象,如下代码所示。

1
2
// 将一个 Person对象输出到输出流中
oos.writeObject(person);

程序 对象序列化

下面程序定义了一个Person类,这个Person类就是一个普通的Java类,只是实现了Serializable接口,该接口标识该类的对象是可序列化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person implements java.io.Serializable {
private static final long serialVersionUID = 3069227031912694124L;
private String name;
private int age;

// 注意此处没有提供无参数的构造器!
public Person(String name, int age) {
System.out.println("有参数的构造器");
this.name = name;
this.age = age;
}
// 省略name与age的setter和getter方法
}

下面程序使用ObjectOutputStream将一个Person对象写入磁盘文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;

public class WriteObject {
public static void main(String[] args) {
try (
// 创建一个ObjectOutputStream输出流
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("object.txt"))// 1号代码
) {
Person per = new Person("孙悟空", 500);
// 将per对象写入输出流
oos.writeObject(per);// 2号代码
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

上面程序中的1号代码创建了一个ObjectOutputStream输出流,这个ObjectOutputStream输出流建立在一个文件输出流的基础之上;
程序中的2号代码使用writeObject()方法将一个Person对象写入输出流。

运行上面程序,将会看到生成了一个object.txt文件,该文件的内容就是Person对象.

对象反序列化步骤

如果希望从二进制流中恢复Java对象,则需要使用反序列化。反序列化的步骤如下。

1. 创建ObjectInputStream

这个输入流是一个处理流,所以必须建立在其他节点流的基础之上。如下代码所示:

1
2
//创建一个ObjectInputstream输入流
ObjectInputstream ois=new ObjectInputstream(new FileInputStream("object.txt"));

2. 调用readObject方法

调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。如下代码所示:

1
2
//从输入流中读取一个Java对象,并将其强制类型转换为 Person类
Person p = (Person)ois.readObject();

程序 对象反序列化

下面程序示范了从刚刚生成的object.txt文件中读取Person对象的步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;

public class ReadObject {
public static void main(String[] args) {
try (
// 创建一个ObjectInputStream输入流
// 1号代码
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))) {
// 从输入流中读取一个Java对象,并将其强制类型转换为Person类
// 2号代码
Person p = (Person) ois.readObject();
System.out.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

上面程序中1号代码将一个文件输入流包装成ObjectInputStream输入流,
2号代码使用readObject()方法读取了文件中的Java对象,这就完成了反序列化过程。
运行结果:

1
2
名字为:孙悟空
年龄为:500

反序列化只能恢复对象的数据

必须指出的是,反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常
ObjectInputStream输入流中的readObject()方法声明抛出了ClassNotFoundException异常,也就是说,当反序列化时找不到对应的Java类时将会引发该异常。

反序列化不会调用构造器

还有一点需要指出:Person类只有一个有参数的构造器,没有无参数的构造器,而且该构造器内有个普通的打印语句。当反序列化读取Java对象时,并没有看到程序调用该构造器,这表明反序列化机制无须通过构造器来初始化Java对象

反序列化读取对象时要按序列化时的写入的顺序读取

如果使用序列化机制向文件中写入了多个Java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取

可序列化类的祖先类要满足的条件

当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参数的构造器,要么也是可序列化的——否则反序列化时将抛出InvalidClassException异常。

如果父类是不可序列化的,只是带有无参数的构造器,则该父类中定义的成员变量值不会序列化到二进制流中