友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第58部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
有相同的接口)。
有两个单独的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(〃Blip2。readExternal〃);
}
}
public class Blips {
public static void main(String'' args) {
System。out。println(〃Constructing objects:〃);
Blip1 b1 = new Blip1();
Blip2 b2 = new Blip2();
try {
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(〃Blips。out〃));
System。out。println(〃Saving objects:〃);
o。writeObject(b1);
o。writeObject(b2);
o。close();
// Now get them back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(〃Blips。out〃));
System。out。println(〃Recovering b1:〃);
b1 = (Blip1)in。readObject();
// OOPS! Throws an exception:
//! System。out。println(〃Recovering b2:〃);
//! b2 = (Blip2)in。readObject();
} catch(Exception e) {
e。printStackTrace();
}
}
} ///:~
320
…………………………………………………………Page 322……………………………………………………………
该程序输出如下:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1。writeExternal
Blip2。writeExternal
Recovering b1:
Blip1 Constructor
Blip1。readExternal
未恢复Blip2 对象的原因是那样做会导致一个违例。你找出了 Blip1 和 Blip2 之间的区别吗?Blip1 的构建
器是“公共的”(public),Blip2 的构建器则不然,这样便会在恢复时造成违例。试试将 Blip2 的构建器
属性变成“public”,然后删除//!注释标记,看看是否能得到正确的结果。
恢复b1 后,会调用 Blip1 默认构建器。这与恢复一个Serializable (可序列化)对象不同。在后者的情况
下,对象完全以它保存下来的二进制位为基础恢复,不存在构建器调用。而对一个Externalizable 对象,所
有普通的默认构建行为都会发生(包括在字段定义时的初始化),而且会调用readExternal()。必须注意这
一事实——特别注意所有默认的构建行为都会进行——否则很难在自己的 Externalizable 对象中产生正确的
行为。
下面这个例子揭示了保存和恢复一个Externalizable 对象必须做的全部事情:
//: Blip3。java
// Reconstructing an externalizable object
import java。io。*;
import java。util。*;
class Blip3 implements Externalizable {
int i;
String s; // No initialization
public Blip3() {
System。out。println(〃Blip3 Constructor〃);
// s; i not initialized
}
public Blip3(String x; int a) {
System。out。println(〃Blip3(String x; int a)〃);
s = x;
i = a;
// s & i initialized only in non…default
// constructor。
}
public String toString() { return s + i; }
public void writeExternal(ObjectOutput out)
throws IOException {
System。out。println(〃Blip3。writeExternal〃);
// You must do this:
out。writeObject(s); out。writeInt(i);
}
public void readExternal(ObjectInput in)
throws IOException; ClassNotFoundException {
System。out。println(〃Blip3。readExternal〃);
// You must do this:
s = (String)in。readObject();
321
…………………………………………………………Page 323……………………………………………………………
i =in。readInt();
}
public static void main(String'' args) {
System。out。println(〃Constructing objects:〃);
Blip3 b3 = new Blip3(〃A String 〃; 47);
System。out。println(b3。toString());
try {
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(〃Blip3。out〃));
System。out。println(〃Saving object:〃);
o。writeObject(b3);
o。close();
// Now get it back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(〃Blip3。out〃));
System。out。println(〃Recovering b3:〃) ;
b3 = (Blip3)in。readObject();
System。out。println(b3。toString());
} catch(Exception e) {
e。printStackTrace();
}
}
} ///:~
其中,字段 s和 i 只在第二个构建器中初始化,不关默认构建器的事。这意味着假如不在readExternal 中初
始化 s 和 i,它们就会成为null (因为在对象创建的第一步中已将对象的存储空间清除为1)。若注释掉跟
随于“You must do this”后面的两行代码,并运行程序,就会发现当对象恢复以后,s 是null,而i 是
零。
若从一个Externalizable 对象继承,通常需要调用writeExternal()和 readExternal()的基础类版本,以便
正确地保存和恢复基础类组件。
所以为了让一切正常运作起来,千万不可仅在 writeExternal()方法执行期间写入对象的重要数据(没有默
认的行为可用来为一个 Externalizable 对象写入所有成员对象)的,而是必须在 readExternal()方法中也
恢复那些数据。初次操作时可能会有些不习惯,因为Externalizable 对象的默认构建行为使其看起来似乎正
在进行某种存储与恢复操作。但实情并非如此。
1。 transient (临时)关键字
控制序列化过程时,可能有一个特定的子对象不愿让Java 的序列化机制自动保存与恢复。一般地,若那个子
对象包含了不想序列化的敏感信息(如密码),就会面临这种情况。即使那种信息在对象中具有“private”
(私有)属性,但一旦经序列化处理,人们就可以通过读取一个文件,或者拦截网络传输得到它。
为防止对象的敏感部分被序列化,一个办法是将自己的类实现为Externalizable,就象前面展示的那样。这
样一来,没有任何东西可以自动序列化,只能在writeExternal()明确序列化那些需要的部分。
然而,若操作的是一个 Serializable 对象,所有序列化操作都会自动进行。为解决这个问题,可以用
transient (临时)逐个字段地关闭序列化,它的意思是“不要麻烦你(指自动机制)保存或恢复它了——我
会自己处理的”。
例如,假设一个Login 对象包含了与一个特定的登录会话有关的信息。校验登录的合法性时,一般都想将数
据保存下来,但不包括密码。为做到这一点,最简单的办法是实现Serializable,并将 password 字段设为
transient。下面是具体的代码:
//: Logon。java
// Demonstrates the 〃transient〃 keyword
322
…………………………………………………………Page 324……………………………………………………………
import java。io。*;
import java。util。*;
class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
Logon(String name; String pwd) {
username = name;
password = pwd;
}
public String toString() {
String pwd =
(password == null) ? 〃(n/a)〃 : password;
return 〃logon info: n 〃 +
〃username: 〃 + username +
〃n date: 〃 + date。toString() +
〃n password: 〃 + pwd;
}
public static void main(String'' args) {
Logon a = new Logon(〃Hulk〃; 〃myLittlePony〃);
System。out。println( 〃logon a = 〃 + a);
try {
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(〃Logon。out〃));
o。writeObject(a);
o。close();
// Delay :
int seconds = 5;
long t = System。currentTimeMillis()
+ seconds * 1000;
while(System。currentTimeMillis() 《 t)
;
// Now get them back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(〃Logon。out〃));
System。out。println(
〃Recovering object at 〃 + new Date());
a = (Logon)in。readObject();
System。out。println( 〃logon a = 〃 + a);
} catch(Exception e) {
e。printStackTrace();
}
}
} ///:~
可以看到,其中的date 和 username 字段保持原始状态(未设成transient),所以会自动序列化。然而,
password 被设为 transient,所以不会自动保存到磁盘;另外,自动序列化机制也不会作恢复它的尝试。输
出如下:
323
…………………………………………………………Page 325……………………………………………………………
logon a = logon info:
username: Hulk
date: Sun Mar 23 18:25:53 PST 1997
password: myLittlePony
Recovering object at Sun Mar 23 18:25 :59 PST 1997
logon a = logon info:
username: Hulk
date: Sun Mar 23 18:25:53 PST 1997
password: (n/a)
一旦对象恢复成原来的样子,password 字段就会变成null 。注意必须用toString()检查password 是否为
null,因为若用过载的“+”运算符来装配一个String 对象,而且那个运算符遇到一个null 句柄,就会造成
一个名为NullPointerException 的违例(新版Java 可能会提供避免这个问题的代码)。
我们也发现 date 字段被保存到磁盘,并从磁盘恢复,没有重新生成。
由于Externalizable 对象默认时不保存它的任何字段,所以transient 关键字只能伴随Serializable 使
用。
2。 Externalizable 的替代方法
若不是特别在意要实现 Externalizable 接口,还有另一种方法可供选用。我们可以实现 Serializable 接
口,并添加(注意是“添加”,而非“覆盖”或者“实现”)名为writeObject()和 readObject()的方法。
一旦对象被序列化或者重新装配,就会分别调用那两个方法。也就是说,只要提供了这两个方法,就会优先
使用它们,而不考虑默认的序列化机制。
这些方法必须含有下列准确的签名:
private void
writeObject(ObjectOutputStream stream)
throws IOException;
private void
readObject(ObjectInputStream stream)
throws IOException; ClassNotFoundException
从设计的角度出发,情况变得有些扑朔迷离。首先,大家可能认为这些方法不属于基础类或者Serializable
接口的一部分,它们应该在自己的接口中得到定义。但请注意它们被定义成“private”,这意味着它们只能
由这个类的其他成员调用。然而,我们实际并不从这个类的其他成员中调用它们,而是由
ObjectOutputStream 和ObjectInputStream 的writeObject()及 readObject()方法来调用我们对象的
writeObject()和readObject()方法 (注意我在这里用了很大的抑制力来避免使用相同的方法名——因为怕
混淆)。大家可能奇怪 ObjectOutputStream 和ObjectInputStream 如何有权访问我们的类的private方法—
—只能认为这是序列化机制玩的一个把戏。
在任何情况下,接口中的定义的任何东西都会自动具有public 属性,所以假若writeObject()和
re
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!