![Java核心技术·卷Ⅱ:高级特性(原书第10版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/937/34339937/b_34339937.jpg)
2.4.1 保存和加载序列化对象
为了保存对象数据,首先需要打开一个ObjectOutputStream对象:
现在,为了保存对象,可以直接使用ObjectOutputStream的writeObject方法,如下所示:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/066-i.jpg?sign=1738865926-NcWbMovMvOXTkMac4VT02mQOfCkzC3T2-0-322640deb2be8e7ba73275b518f47abb)
为了将这些对象读回,首先需要获得一个ObjectInputStream对象:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/067-i.jpg?sign=1738865926-xm3fvDmiCBL0g7WefvS3CBm1Y1vsFwer-0-1f802ebab40cdfc0dc148c277d0393ae)
然后,用readObject方法以这些对象被写出时的顺序获得它们:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/067-2-i.jpg?sign=1738865926-kbSWKQ3MKplABVAcStZXNcswL6YvWKzY-0-e1181dfb52aaf4c5f3fa7570e3ef1202)
但是,对希望在对象输出流中存储或从对象输入流中恢复的所有类都应进行一下修改,这些类必须实现Serializable接口:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/067-3-i.jpg?sign=1738865926-7khPmGALbU3KFAnKRgPygOm4fvFxCcxH-0-7066946e1ac96bb557f08496e3e03ccf)
Serializable接口没有任何方法,因此你不需要对这些类做任何改动。在这一点上,它与在卷Ⅰ第6章中讨论过的Cloneable接口很相似。但是,为了使类可克隆,你仍旧需要覆盖Object类中的clone方法,而为了使类可序列化,你不需要做任何事。
注意:你只有在写出对象时才能用writeObject/readObject方法,对于基本类型值,你需要使用诸如writeInt/readInt或writeDouble/readDouble这样的方法。(对象流类都实现了DataInput/DataOutput接口。)
在幕后,是ObjectOutputStream在浏览对象的所有域,并存储它们的内容。例如,当写出一个Employee对象时,其名字、日期和薪水域都会被写出到输出流中。
但是,有一种重要的情况需要考虑:当一个对象被多个对象共享,作为它们各自状态的一部分时,会发生什么呢?
为了说明这个问题,我们对Manager类稍微做些修改,假设每个经理都有一个秘书:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/067-4-i.jpg?sign=1738865926-FROW27tVYMdwSYq1RvC5EE2R1zx6Xnaz-0-fb8dd672b4f40d7f5e6dd0dd883aa385)
现在每个Manager对象都包含一个表示秘书的Employee对象的引用,当然,两个经理可以共用一个秘书,正如图2-5和下面的代码所示的那样:
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/067-5-i.jpg?sign=1738865926-PygGpOyXI6XZvoXSVj5FbiQSQGwgB1VJ-0-b9269e364eaf9a03f664e7444d1c4a79)
保存这样的对象网络是一种挑战,在这里我们当然不能去保存和恢复秘书对象的内存地址,因为当对象被重新加载时,它可能占据的是与原来完全不同的内存地址。
与此不同的是,每个对象都是用一个序列号(serial number)保存的,这就是这种机制之所以称为对象序列化的原因。下面是其算法:
·对你遇到的每一个对象引用都关联一个序列号(如图2-6所示)。
·对于每个对象,当第一次遇到时,保存其对象数据到输出流中。
·如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”。
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/2a5-i.jpg?sign=1738865926-oKOglALaQy5F8aUp3GHwhfTc4rg8ImtX-0-d820dd89bd05fb6d6f0a12ef301b8577)
图2-5 两个经理可以共用一个共有的雇员
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/2a6-i.jpg?sign=1738865926-K42Rl0TVpTF3vcFM6z5x0gBfqO6ntzz2-0-6ae12e679820675e1f31927a0e0030d0)
图2-6 一个对象序列化的实例
在读回对象时,整个过程是反过来的。
·对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。
·当遇到“与之前保存过的序列号为x的对象相同”标记时,获取与这个顺序号相关联的对象引用。
注意:在本章中,我们使用序列化将对象集合保存到磁盘文件中,并按照它们被存储的样子获取它们。序列化的另一种非常重要的应用是通过网络将对象集合传送到另一台计算机上。正如在文件中保存原生的内存地址毫无意义一样,这些地址对于在不同的处理器之间的通信也是毫无意义的。因为序列化用序列号代替了内存地址,所以它允许将对象集合从一台机器传送到另一台机器。
程序清单2-3是保存和重新加载Employee和Manager对象网络的代码(有些对象共享相同的表示秘书的雇员)。注意,秘书对象在重新加载之后是唯一的,当newStaff[1]被恢复时,它会反映到经理们的secretary域中。
程序清单2-3 objectStream/ObjectStreamTest.java
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/069-i.jpg?sign=1738865926-LJDTpV9x5pG814g7Pzbr8TXl5JACNKDJ-0-5fc459aa198a6def4561483a24828aec)
![](https://epubservercos.yuewen.com/F21227/18365861501241106/epubprivate/OEBPS/Images/070-i.jpg?sign=1738865926-dEyfxHBqj2Udr979CqNSCjrN9gyj6y24-0-5f4e4b8e2d1dfba3897ec78e4eaf3eaf)
java.io.ObjectOutputStream 1.1
·ObjectOutputStream(OutputStream out)
创建一个ObjectOutputStream使得你可以将对象写出到指定的OutputStream。
·void writeObject(Object obj)
写出指定的对象到ObjectOutputStream,这个方法将存储指定对象的类、类的签名以及这个类及其超类中所有非静态和非瞬时的域的值。
java.io.ObjectInputStream 1.1
·ObjectInputStream(InputStream in)
创建一个ObjectInputStream用于从指定的InputStream中读回对象信息。
·Object readObject()
从ObjectInputStream中读入一个对象。特别是,这个方法会读回对象的类、类的签名以及这个类及其超类中所有非静态和非瞬时的域的值。它执行的反序列化允许恢复多个对象引用。