Java编程思想第4版[中文版](PDF格式)-第86部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
象序列化来传输参数和返回值。RMI 将在第 15章作具体讨论。
对象的序列化也是 Java Beans 必需的,后者由Java 1。1 引入。使用一个Bean 时,它的状态信息通常在设计
期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化
完成。
对象的序列化处理非常简单,只需对象实现了 Serializable 接口即可(该接口仅是一个标记,没有方法)。
在Java 1。1 中,许多标准库类都发生了改变,以便能够序列化——其中包括用于基本数据类型的全部封装
器、所有集合类以及其他许多东西。甚至 Class 对象也可以序列化(第 11章讲述了具体实现过程)。
为序列化一个对象,首先要创建某些OutputStream 对象,然后将其封装到 ObjectOutputStream 对象内。此
时,只需调用writeObject() 即可完成对象的序列化,并将其发送给OutputStream。相反的过程是将一个
InputStream封装到 ObjectInputStream 内,然后调用readObject()。和往常一样,我们最后获得的是指向
一个上溯造型Object 的句柄,所以必须下溯造型,以便能够直接设置。
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄
并保存那些对象;接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对象
网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象
序列化机制,那么在代码里追踪所有这些链接时可能会显得非常麻烦。在另一方面,由于Java 对象的序列化
似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子对
序列化机制进行了测试。它建立了许多链接对象的一个“Worm ”(蠕虫),每个对象都与Worm 中的下一段链
接,同时又与属于不同类(Data )的对象句柄数组链接:
//: Worm。java
// Demonstrates object serialization in Java 1。1
import java。io。*;
class Data implements Serializable {
private int i;
Data(int x) { i = x; }
public String toString() {
return Integer。toString(i);
}
}
public class Worm implements Serializable {
// Generate a random int value:
private static int r() {
return (int)(Math。random() * 10);
}
private Data'' d = {
new Data(r()); new Data(r()); new Data(r())
};
private Worm next;
private char c;
// Value of i == number of segments
Worm(int i; char x) {
System。out。println(〃 Worm constructor: 〃 + i);
c = x;
if(……i 》 0)
next = new Worm(i; (char)(x + 1));
}
Worm() {
System。out。println(〃Default constructor〃);
}
316
…………………………………………………………Page 318……………………………………………………………
public String toString() {
String s = 〃:〃 + c + 〃(〃;
for(int i = 0; i 《 d。length; i++)
s += d'i'。toString();
s += 〃)〃;
if(next != null)
s += next。toString();
return s;
}
public static void main(String'' args) {
Worm w = new Worm(6; 'a');
System。out。println(〃w = 〃 + w);
try {
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream(〃worm。out〃));
out。writeObject(〃Worm storage〃);
out。writeObject(w);
out。close(); // Also flushes output
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(〃worm。out〃));
String s = (String)in。readObject();
Worm w2 = (Worm)in。readObject();
System。out。println(s + 〃; w2 = 〃 + w2);
} catch(Exception e) {
e。printStackTrace();
}
try {
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
ObjectOutputStream out =
new ObjectOutputStream(bout);
out。writeObject(〃Worm storage〃);
out。writeObject(w);
out。flush();
ObjectInputStream in =
new ObjectInputStream(
new ByteArrayInputStream(
bout。toByteArray()));
String s = (String)in。readObject();
Worm w3 = (Worm)in。readObject();
System。out。println(s + 〃; w3 = 〃 + w3);
} catch(Exception e) {
e。printStackTrace();
}
}
} ///:~
更有趣的是,Worm 内的Data 对象数组是用随机数字初始化的(这样便不用怀疑编译器保留了某种原始信
息)。每个 Worm 段都用一个 Char 标记。这个 Char 是在重复生成链接的Worm 列表时自动产生的。创建一个
Worm 时,需告诉构建器希望它有多长。为产生下一个句柄(next ),它总是用减去 1 的长度来调用Worm 构
317
…………………………………………………………Page 319……………………………………………………………
建器。最后一个next 句柄则保持为null (空),表示已抵达Worm 的尾部。
上面的所有操作都是为了加深事情的复杂程度,加大对象序列化的难度。然而,真正的序列化过程却是非常
简单的。一旦从另外某个流里创建了ObjectOutputStream ,writeObject()就会序列化对象。注意也可以为
一个String 调用 writeObject() 。亦可使用与DataOutputStream 相同的方法写入所有基本数据类型(它们
有相同的接口)。
有两个单独的try 块看起来是类似的。第一个读写的是文件,而另一个读写的是一个 ByteArray (字节数
组)。可利用对任何DataInputStream 或者DataOutputStream 的序列化来读写特定的对象;正如在关于连网
的那一章会讲到的那样,这些对象甚至包括网络。一次循环后的输出结果如下:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage; w2 = :a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage; w3 = :a(262):b(100):c(396):d(480):e(316):f(398)
可以看出,装配回原状的对象确实包含了原来那个对象里包含的所有链接。
注意在对一个Serializable (可序列化)对象进行重新装配的过程中,不会调用任何构建器(甚至默认构建
器)。整个对象都是通过从 InputStream 中取得数据恢复的。
作为Java 1。1 特性的一种,我们注意到对象的序列化并不属于新的 Reader 和 Writer 层次结构的一部分,而
是沿用老式的 InputStream 和OutputStream 结构。所以在一些特殊的场合下,不得不混合使用两种类型的层
次结构。
10。9。1 寻找类
读者或许会奇怪为什么需要一个对象从它的序列化状态中恢复。举个例子来说,假定我们序列化一个对象,
并通过网络将其作为文件传送给另一台机器。此时,位于另一台机器的程序可以只用文件目录来重新构造这
个对象吗?
回答这个问题的最好方法就是做一个实验。下面这个文件位于本章的子目录下:
//: Alien。java
// A serializable class
import java。io。*;
public class Alien implements Serializable {
} ///:~
用于创建和序列化一个 Alien 对象的文件位于相同的目录下:
//: FreezeAlien。java
// Create a serialized output file
import java。io。*;
public class FreezeAlien {
public static void main(String'' args)
throws Exception {
ObjectOutput out =
new ObjectOutputStream(
new FileOutputStream(〃file。x〃));
Alien zorcon = new Alien();
318
…………………………………………………………Page 320……………………………………………………………
out。writeObject(zorcon);
}
} ///:~
该程序并不是捕获和控制违例,而是将违例简单、直接地传递到main()外部,这样便能在命令行报告它们。
程序编译并运行后,将结果产生的 file。x 复制到名为 xfiles 的子目录,代码如下:
//: ThawAlien。java
// Try to recover a serialized file without the
// class of object that's stored in that file。
package c10。xfiles;
import java。io。*;
public class ThawAlien {
public static void main(String'' args)
throws Exception {
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(〃file。x〃));
Object mystery = in。readObject();
System。out。println(
mystery。getClass()。toString());
}
} ///:~
该程序能打开文件,并成功读取mystery 对象中的内容。然而,一旦尝试查找与对象有关的任何资料——这
要求Alien 的Class 对象——Java 虚拟机(JVM)便找不到Alien。class (除非它正好在类路径内,而本例理
应相反)。这样就会得到一个名叫 ClassNotFoundException 的违例(同样地,若非能够校验Alien 存在的证
据,否则它等于消失)。
恢复了一个序列化的对象后,如果想对其做更多的事情,必须保证JVM 能在本地类路径或者因特网的其他什
么地方找到相关的。class文件。
10。9。2 序列化的控制
正如大家看到的那样,默认的序列化机制并不难操纵。然而,假若有特殊要求又该怎么办呢?我们可能有特
殊的安全问题,不希望对象的某一部分序列化;或者某一个子对象完全不必序列化,因为对象恢复以后,那
一部分需要重新创建。
此时,通过实现Externalizable 接口,用它代替Serializable 接口,便可控制序列化的具体过程。这个
Externalizable 接口扩展了 Serializable,并增添了两个方法:writeExternal()和readExternal()。在序
列化和重新装配的过程中,会自动调用这两个方法,以便我们执行一些特殊操作。
下面这个例子展示了Externalizable 接口方法的简单应用。注意Blip1 和Blip2 几乎完全一致,除了极微小
的差别(自己研究一下代码,看看是否能发现):
//: Blips。java
// Simple use of Externalizable & a pitfall
import java。io。*;
import java。util。*;
class Blip1 implements Externalizable {
public Blip1() {
System。out。println(〃Blip1 Constructor〃);
}
public void writeExternal(ObjectOutput out)
319
…………………………………………………………Page 321……………………………………………………………
throws IOException {
System。out。println(〃Blip1。writeExternal〃);
}
public void readExternal(ObjectInput in)
throws IOException; ClassNotFoundException {
System。out。println(〃Blip1。readExternal〃);
}
}
class Blip2 implements Externalizable {
Blip2() {
System。out。println(〃Blip2 Constructor〃);
}
public void writeExternal(ObjectOutput out)
throws IOException {
System。out。println(〃Blip2。writeExternal〃);
}
public void readExternal(ObjectInput in)
throws IOException; ClassNotFoundException {
System。out。println(〃