通过实现java.io.Serializable接口的类,可以启用类的可序列化性。 未实现此接口的类将不会对其状态进行序列化或反序列化。 可序列化类的所有子类型本身都是可序列化的。 序列化接口没有方法或字段,仅用于标识可序列化的语义。
为了允许序列化不可序列化类的子类型,该子类型可以承担保存和恢复超类型的公共,受保护和(如果可访问)程序包字段的状态的责任。 仅当其扩展的类具有可访问的无参数构造函数以初始化类的状态时,该子类型才可以承担此责任。 如果不是这样,则声明一个可序列化的类是错误的。 该错误将在运行时检测到。
反序列化期间,将使用该类的公共或受保护的无参数构造函数来初始化不可序列化类的字段。 必须对可序列化的子类访问no-arg构造函数。 可序列化子类的字段将从流中恢复。
在遍历图形时,可能会遇到不支持Serializable接口的对象。 在这种情况下,将抛出NotSerializableException并标识不可序列化对象的类。
在序列化和反序列化过程中需要特殊处理的类必须实现具有以下确切签名的特殊方法:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
writeObject方法负责为其特定类写入对象的状态,以便相应的readObject方法可以还原它。 保存对象字段的默认机制可以通过调用out.defaultWriteObject来调用。 该方法无需将自身与属于其超类或子类的状态相关。 通过使用writeObject方法将单个字段写入ObjectOutputStream或使用DataOutput支持的原始数据类型的方法来保存状态。
readObject方法负责从流中读取并还原类字段。 它可以调用in.defaultReadObject来调用用于还原对象的非静态和非瞬态字段的默认机制。 defaultReadObject方法使用流中的信息为流中保存的对象的字段分配当前对象中相应命名的字段。 这处理了类已演化为添加新字段的情况。 该方法无需将自身与属于其超类或子类的状态相关。 通过使用writeObject方法将单个字段写入ObjectOutputStream或使用DataOutput支持的原始数据类型的方法来保存状态。
如果序列化流未将给定类列出为要反序列化的对象的超类,则readObjectNoData方法负责为其特定类初始化对象的状态。 在接收方使用与发送方不同的反序列化实例类的版本,并且接收方的版本扩展了发送方的版本未扩展的类的情况下,可能会发生这种情况。 如果序列化流已被篡改,也可能会发生这种情况。 因此,尽管源流“充满敌意”或不完整,但readObjectNoData对于正确初始化反序列化的对象很有用。
当将对象写入流时,需要指定要使用的替代对象的可序列化类应使用确切的签名实现此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
如果该writeReplace方法存在,则可以通过序列化调用,并且可以从正在序列化的对象的类中定义的方法访问该方法。 因此,该方法可以具有私有,受保护和程序包私有的访问。 对该方法的子类访问遵循Java可访问性规则。
从流中读取实例时需要指定替换的类应使用具有确切签名的特殊方法来实现。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此readResolve方法遵循与writeReplace相同的调用规则和可访问性规则。
序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,该序列号在反序列化期间用于验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。 如果接收方已为该对象加载了一个与相应发送方的类具有不同的serialVersionUID的类,则反序列化将导致InvalidClassException 。 可序列化的类可以通过声明一个名称为"serialVersionUID"的字段来显式声明其自己的serialVersionUID,该字段必须是静态的,最终的且类型为long :
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面为该类计算默认的serialVersionUID值,如Java(TM)对象序列化规范中所述。 但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,该类详细信息可能会因编译器的实现而有所不同,因此可能在反序列化期间导致意外的InvalidClassException 。 因此,为了保证不同Java编译器实现之间的serialVersionUID值一致,可序列化的类必须声明一个显式的serialVersionUID值。 还强烈建议显式serialVersionUID声明在可能的情况下使用private修饰符,因为此类声明仅适用于立即声明的类-serialVersionUID字段作为继承成员没有用。 数组类无法声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,无需匹配serialVersionUID值。