如果我们不是特别想要实现 Externalizable 接口,那么就还有另一种方法。我们可以实现
Serializable 接口,并添加(注意我说的是“添加”,而非“重载”或者“实现”)名为
writeObject()和 readObject()的方法。这样一旦对象被序列化或者被反序列化,就会自
动地分别调用这两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是缺
省的序列化机制。
这些方法必须具有准确的方法签名:
private voidwriteObject(ObjectOutputStream stream)
throwsIOException;
private voidreadObject(ObjectInputStream stream)
throwsIOException, ClassNotFoundException
从设计的观点来看,现在事情变得真是不可思议。首先,我们可能会认为由于这些方法不是
基类或者Serializable 接口的一部分,所以应该在它们自己的接口中进行定义。但是注意
它们被定义成了private,这意味着它们仅能被这个类的其他成员调用。然而,实际上我们
并没有从这个类的其他方法中调用它们,而是 ObjectOutputStream 和
ObjectInputStream 对象的 writeObject( )和 readObject( )方法调用我们对象的
writeObject( ) 和 readObject( )方法(注意关于这里用到的相同方法名,我尽量抑制住
不去谩骂。一句话:混乱)。你可能想知道 ObjectOutputStream 和 ObjectInputStream
对象是怎样访问你的类中的 private 方法的。我们只能假设这正是序列化神奇的一部分。
在任何情况下,在接口中定义的所有东西都自动是 public 的,因此如果 writeObject( ) 和
readObject( )必须是 private 的,那么它们不会是接口的一部分。因为我们必须要完全遵
循其方法签名,所以其效果就和实现了接口一样。
在你调用ObjectOutputStream.writeObject()时,会检查你所传递的 Serializable 对
象,看看是否实现了它自己的 writeObject( )。如果是这样,就跳过正常的序列化过程并
调用它的writeObject( )。readObject( )的情形与此相同。
还有另外一个技巧。在我们的 writeObject()内部,可以调用 defaultWriteObject()来选
择执行缺省的writeObject()。类似地,在 readObject()内部,我们可以调用
defaultReadObject()。下面这个简单的例子演示了如何对一个 Serializable 对象的存储
与恢复进行控制:
//:c12:SerialCtl.java
// Controllingserialization by adding your own
// writeObject()and readObject() methods.
importcom.bruceeckel.simpletest.*;
importjava.io.*;
public class SerialCtlimplements Serializable {
private static Test monitor = new Test();
private String a;
private transient String b;
public SerialCtl(String aa, String bb) {
a = "Not Transient: " + aa;
b = "Transient: " + bb;
}
public String toString() { return a +"\n" + b; }
private void writeObject(ObjectOutputStreamstream)
throws IOException {
stream.defaultWriteObject();
stream.writeObject(b);
}
private void readObject(ObjectInputStreamstream)
throws IOException, ClassNotFoundException{
stream.defaultReadObject();
b = (String)stream.readObject();
}
public static void main(String[] args)
throws IOException, ClassNotFoundException{
SerialCtl sc = newSerialCtl("Test1", "Test2");
System.out.println("Before:\n" +sc);
ByteArrayOutputStream buf= newByteArrayOutputStream();
ObjectOutputStream o = newObjectOutputStream(buf);
o.writeObject(sc);
// Now get it back:
ObjectInputStream in = new ObjectInputStream(
newByteArrayInputStream(buf.toByteArray()));
SerialCtl sc2 = (SerialCtl)in.readObject();
System.out.println("After:\n" +sc2);
monitor.expect(new String[] {
"Before:",
"Not Transient: Test1",
"Transient: Test2",
"After:",
"Not Transient: Test1",
"Transient: Test2"
});
}
} ///:~
在这个例子中,有一个 String 域是普通域,而另一个是 transient(瞬时)域,用来证明
非 transient 域由 defaultWriteObject()方法保存,而 transient 域必须在程序中明确保
存和恢复。域是在构造器内部而不是在定义处进行初始化的,以此可以证实它们在反序列化
期间没有被一些自动机制初始化。
如果我们打算使用缺省机制写入对象的非 transient 部分,那么我们必须调用
defaultWriteObject()作为 writeObject( )中的第一个操作,并让defaultReadObject()
作为 readObject( )中的第一个操作。这些都是奇怪的方法调用。例如,如果我们正在为
ObjectOutputStream调用 defaultWriteObject( )并且没有传递任何参数,然而不知何
故它却可以运行,并且知道对象的引用以及如何写入非 transient 部分。奇怪之极。
对 Transient 对象的存储和恢复使用到了我们比较熟悉的代码。请再考虑一下在这里发生
了什么事情。在main()中,创建 SerialCtl 对象,然后将其序列化到 ObjectOutputStream
里(注意在这种情况下,使用的是缓冲区而不是文件——这对于 ObjectOutputStream 来
说则是完全一样的)。序列化发生在下面这行代码当中:
o.writeObject(sc);
writeObject()方法必须检查 sc,判断它是否拥有自己的 writeObject()方法(不是检查接
口——这里根本就没有接口,也不是检查类的类型,而是利用反射来来真正地搜索方法)。
如果有,那么就会使用它。对 readObject()也采用了类似的方法。或许这是解决这个问题
唯一切实可行的方法,但它确实有点古怪。