对象序列化

来源:互联网 发布:陆地卫星数据特点 编辑:程序博客网 时间:2024/06/06 03:21

当你需要存储相同类型的数据时,使用固定长度的纪录格式是一个不错的选择。但是,在面向对象程序中创建的对象很少全部都具有相同的类型。例如,你可能有一个称为 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--20world--0分析:并不会自动序列化,执行上述方法,对于 transient 标记了的值,可以自定义实现。对于*/

除了让序列化机制来保存和恢复对象数据,类还可以定义它自己的机制。为了做到这一点,这个类必须实现 Externalizable 接口,并定义如下两个方法:

public void readExternal(ObjectInputStream in) throws IOException,ClassNotFoundException;public void writeExternal(ObjectOutputStream out) throws IOException;

与readObject/writeObject 不同,这些方法包括对超类数据在内的整个对象的存储和恢复负责,而序列化机制在流中仅仅只是记录该对象所属的类。在读入可外部化的类时,对象流将用无参构造器创建一个对象,然后调用readExternal 方法。

//添加新类User2class 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 核心技术卷Ⅱ (原书第九版)

原创粉丝点击