当你需要存储相同类型的数据时,使用固定长度的纪录格式是一个不错的选择。但是,在面向对象程序中创建的对象很少全部都具有相同的类型。例如,你可能有一个称为 staff 的数组,它名义上是一个 Employee 纪录数组,但是实际上却包含诸如 Manager 这样的子类实例。
Java语言支持一种称为对象序列化的非常通用的机制,它可以将任何对象写出到流中,并在之后将其读回。
保存和读回对象数据
保存对象数据:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("..."));
现在可以直接使用ObjectOutputStream的writeObject 方法:
Employee harry = new Employee("Harry Hacker");
Manager boss = new Manager("Carl c");
out.writeObject(harry);
out.writeObject(boss);
读回对象:
ObjectInputStream in = new ObjectInputStream(new FileInputStream("..."));
然后,使用,readObject 方法以这些对象被写出时的顺序获得它们:
Employee e1 = (Employee)in.readObject();
Employee e1 = (Employee)in.readObject();
但是,对希望在对象流中存储或恢复的所有类都必须实现Serializable 接口:
class Employee implements Serializable{...}
只有在写出对象时才能用writeObject / readObject 方法,对于基本类型值,你需要使用诸如writeInt/readInt 或 writeDouble/readDouble这样的方法。(对象流类都实现了DataInput/DataOutput 接口。)
对象序列化机制
当一个对象被多个对象共享,作为它们各自状态的一部分时,会发生什么呢?
先对Manager 类做些修改,假设每个经理都有一个秘书。现在每个Manager 对象都包含一个表示秘书的Employee对象的引用,当然,两个经理可以共用一个秘书。保存这样的对象网络是一种挑战,在这里我们当然不能去保存和恢复秘书对象的内存地址,因为当对象被重新加载时,它可能占据的是与原来完全不同的地址。
于此不同的是,每个对象都是用一个序列号保存的,这就是这种机制之所以被称为对象序列化的原因。下面是其算法:
- 对你遇到的每一个对象引用都关联一个序列号。
- 对于每个对象,当第一次遇到,保存其对象数据到流中。
- 如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”。在读回对象时,整个过程都是反过来的。
- 对于流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。
- 当遇到“与之前保存过的序列号为x的对象相同”标记时,获取与这个顺序号相关联的对象引用。
修改默认的序列化机制
某些数据域是不可以序列化的。Java 拥有一种很简单的机制来防止这种域被序列化,那么就是将它们标记成是 * transient* 的。瞬时的域在对象被序列化时总是被跳过的。
序列化机制为单个的类提供了一种方式,去向默认的读写行为添加验证或任何其他想要的行为。可序列化的类可以定义具有下列签名的方法:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
private void wirteObjcet(ObjectOutputStream out) throws IOException;
之后,数据域就再也不会被自动序列化,取而代之的是调用这些方法。这些方法只需要保存和加载自身的数据域,而不需要关心超类数据和任何其他类的信息。
import java.io.*;
class User implements Serializable{
public String name = "world";
public transient int age = 10;
}
public class ObjectIOTest implements Serializable{
private String name = "world";
public transient int age = 10;
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeUTF("hello");
out.writeInt(20);
System.out.println("执行writeObject方法");
}
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
name = in.readUTF();
age = in.readInt(); //只需要保存和加载自身的数据域,而不需要关心超类数据和任何其他类的信息。
System.out.println("执行readObject方法");
}
public static void main(String[] args) throws Exception{
//保存对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.dat"));
ObjectIOTest oio = new ObjectIOTest();
out.writeObject(oio);
User user = new User(); //未定义上述两个方法的类
out.writeObject(user);
//读回对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.dat"));
oio = (ObjectIOTest)in.readObject();
user = (User)in.readObject();
System.out.println(oio.name+"--"+oio.age);
System.out.println(user.name+"--"+user.age);
}
}
/*
运行结果:
执行writeObject方法
执行readObject方法
hello--20
world--0
分析:并不会自动序列化,执行上述方法,对于 transient 标记了的值,可以自定义实现。对于
*/
除了让序列化机制来保存和恢复对象数据,类还可以定义它自己的机制。为了做到这一点,这个类必须实现 Externalizable 接口,并定义如下两个方法:
public void readExternal(ObjectInputStream in) throws IOException,ClassNotFoundException;
public void writeExternal(ObjectOutputStream out) throws IOException;
与readObject/writeObject 不同,这些方法包括对超类数据在内的整个对象的存储和恢复负责,而序列化机制在流中仅仅只是记录该对象所属的类。在读入可外部化的类时,对象流将用无参构造器创建一个对象,然后调用readExternal 方法。
//添加新类User2
class User2 extends User implements Externalizable{
public String sex = "男";
public User2(){
System.out.println("执行User2()");
}
public void writeExternal(ObjectOutput out) throws IOException{
System.out.println("执行writeExternal()");
out.writeUTF("女");
out.writeInt(20);
}
public void readExternal(ObjectInput in) throws IOException{
System.out.println("执行readExternal()");
sex = in.readUTF();
age = in.readInt();
}
}
//main()方法:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.dat"));
User2 user = new User2(); //定义上述两个方法的类
out.writeObject(user);
//读回对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.dat"));
user = (User2)in.readObject();
System.out.println(user.name+"--"+user.age+"--"+user.sex);
/*
运行结果:
执行User2()
执行writeExternal()
执行User2()
执行readExternal()
world--20--女
*/
readObject 和 writeObject 方法是私有的,并且只能被序列化机制调用。与此不同的是,readExternal 和 writeExternal 方法是公共的。特别是,readExternal 还潜在地允许修改现有对象的状态。
以上内容和部分代码来源于Java 核心技术卷Ⅱ (原书第九版)