对象序列化

当你需要存储相同类型的数据时,使用固定长度的纪录格式是一个不错的选择。但是,在面向对象程序中创建的对象很少全部都具有相同的类型。例如,你可能有一个称为 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 核心技术卷Ⅱ (原书第九版)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值