并不是每一个对象都可以写到输出流。可以写入输出流中的对象称为
可序列化的 (serializable)。可序列化对象的类必须实现 Serializable 接
口。
Serializable 接口是一种标记性接口。它没有方法,所以,实现
Serializable 接口的类不需要添加额外的代码。要实现这个接口可以启动 Java
的序列机制,自动执行存储对象和数组的过程。
为了体会这个自动功能和理解对象是如何存储的,考虑一下不使用这一
功能,储存一个对象需要做哪些工作。假设要存储一个 JButton 对象,需要存
储该对象属性 (例如 color、font、text 和 alignment等)的全部当前值。由
于 JButton 是 AbstractButton 的一个子类,所以,必须把 AbstractButton
的属性值和 AbstractButton 所有父类的属性值都储存起来。如果某个属性是对
象类型的(如 background 是 Color 类型),存储它就要求存储这个对象的所
有属性值。可以看到,这是一个非常烦琐冗长的过程。幸运的是不必人工完成这
一过程。Java 提供自动写对象这一过程的内在机制。这个过程称为 对象序列化
(object serialization),在 ObjectOutputStream 中实现。与此相反,读取
对象的过程称作 对象解读序列化 (object deserialization),它是在
ObjectInputStream 类中实现的。
许多 Java API 中的类都实现了 Serializable 接口。工具类如
java.util.Date 以及所有的 Swing GUI 组件类都实现 Serializable 接口。试
图存储一个不支持 Serializable 接口的对象会引起
NotSerializableException 异常。
存储一个可序列化对象时,会对该对象的类进行编码,编码包括类名、
类的签名、对象实例变量的值以及任何从初始对象引用的其他对象闭包,但是不
存储对象静态变量的值。
注 如果一个对象是 Serializable 的实例,但它包含一个非序列化的数据域,
该对象是可序列化的吗?答案是否定的。为了使该对象是可序列化的,需要给这
些数据域加上关键字 transient,告诉 Java 虚拟机将对象写入对象流时忽略这
些数据域。考虑下面的类:
public class Foo implements java.io.Serializable {
private int v1;
private static double v2;
private transient A v3 = new A();
}
class A {} // A is not serializable
对 Foo 类的对象进行序列化时,只需序列化变量 v1。因为 v2 是一个静态变
量,所以不进行序列化。因为 v3 标记为 transient,所以也不进行序列化。如
果 v3 没有标记为 transient,将会发生异常
java.io.NotSerializableException 。
注 如果一个对象不止一次写入对象流,会存储对象的多份副本吗?答案是不会。
第一次存储一个对象时,给它创建一个序列号。Java 虚拟机将对象的所有内容
和序列号一起写入对象流。以后每次存储时,如果再写入相同的对象,只存储序
列号。读出这些对象时,它们的引用相同,因为实际上仅在内存中存储了一个对
象。