JDK 1.8 API阅读与翻译(3) Serializable
Author: Alex Wang
Date: March 3 2019
public interface Serializable
Serializability of a class is enabled by the class implementing the java.io.Serializable interface. Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are them serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.
To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.
During deserialization, the fileds of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.
When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will be thrown and will identify the class of the non-serializable object.
Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signature:
private void writeObject(java.io.ObjectOutputStream out) throw IOException.
private void readObject(java.io.ObjectInputStream in) throw IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore if. The default mechanism for saving the Object's fields can be invoked by calling out. defaultWriteObject. The method does not need concern itself with the state belonging to its superclasses or subclasses. State is save by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
The readObject method is responsible for reading from the stream and restoring the class fileds. It may call in.defaultReadObject to invoke the default merchanism for restoring the object's non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields. The method does not need to concern itself with the state belonging to its superclass or subclasses. State is saved by wirting the individual fields to the ObjectOutputStream using the writeObject method or by using the medhods for primitive data types supported by DataOutput.
The readObjectNoData method is responsible for initializaing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the reveiver's version extends classes that are not extended by the sender's version. This may also occur if the serializatiopn stream has been tampered; hence, readObjectNoData is useful for initializaing deserialized objects properly despite a "hostile" or incomplete source stream.
Serializable classes that need to designate an alternative obect to be used when writing an object to the stream should implement this special method with the exact signature:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have privcate, protected and package-private acess. Subclass access when an instance of it is read from the stream should implement this special method with the exact signature.
ANY-ACCESS-MODIFIFER Object readResolve() throws ObjectStreamException;
This readResolve method follows the same invocation rules and accessiblility rules as writeReplace.
The serialization runtime associates with each serializable class a version number, called a seriaVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static, final, and of type long:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculatue a default serialVersionUID value for that class based on various aspects of the class, as described in the Java Object Serialization Specification. However, it is strongly recommended that all serializable classes explicity declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations , and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value accross different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declaring class--serialVersionUID fields are not useful as inherited members. Arrary classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.
since
JDK1.1
See Also:
ObjectOutputStream, ObjectInputStream, ObjectOutput, ObjectInput, Externalizable
Java.io.Serializable接口的实现类启用了类的可序列化。没有实现这个接口的类并不会将任何状态序列化或者反序列化。可序列化勒的所有子类都是可以序列化的。序列化接口没有方法或者字段,仅仅用于标识可序列化的语义。
为了让非序列化类的子类可以被序列化,这个子类可以承担保存和恢复超类的pulic,protected,package字段(如果可访问的话)。只有当它拓展的类具有可访问无参构造函数来初始化类的状态时,子类才可以承担这样的责任。如果不是这种情况,那么就不能声明一个类是可序列化的。这个错误将在运行的时候被检测出来。
在反序列化区间,不可序列化的类中的字段会被pulblic或者protected的无参构造器初始化。必须对可序列化的子类访问无参构造器,可序列化的子类字段将从流中被恢复。
在遍历一个图时,可能会遇到不支持Serializable接口的情况。这种情况下,将会抛出NotSerializableException异常,并将这个类识别为不可序列化的对象。
在序列化和反序列化的过程中需要特殊处理的类必须要使用这些精确的声明来实现特殊的方法:
private void writeObject(java.io.ObjectOutputStream out) throw IOException.
private void readObject(java.io.ObjectInputStream in) throw IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
writeObject方法负责为其特定类编写对象的状态,以便相应的readObject方法可以恢复它。可以通过调用out.defaultWriteObject来调用保存的Object字段的默认机制。该方法不需要关注属于其超类或者自鳄梨的状态。通过使用writeObject方法或者使用DataOutput支持的原始数据类型的状态。通过使用writeObject方法或者使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
readObject方法负责从流中读取并恢复类的字段。它可以调用.in.defaultReadObject来嗲用恢复对象的非静态类和非瞬态字段的默认机制。defaultReadObject方法使用流中的信息来制定流中保存的对象的字段以及当前对象中相应命名的字段。这处理了类在演变为添加新字段时的情况。该方法不需要关注其属于超类或者子类的状态。通过使用writeObject方法或者使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
readObjectNoData方法负责在序列化流未将给定类列为要反序列化的对象的超类的情况下初始化其特定类的对象状态。如果接收方使用和发送方不同版本的反序列化实例的类,并且接受方的版本拓展了未由发送方拓展的类,就可能发生这种情况。如果序列化流已经被篡改,也可能发生这种状况;因此,尽管存在“恶意”的或者不完整的流,readObjectNoData仍可以用来初始化反序列化对象。
需要制定在将对象写入流时使用的备用对象的可序列化类应该使用确切的声明来实现这个特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
如果方法存在,则可以通过序列化调用此writeReplace方法,并且可以从要序列化的对象的类中定义的方法访问该方法。因此,该方法可以具有private,protected,package-private类型方法的访问。子类对方法的访问遵循java的访问规则。
从流中读取实例时需要制定替换的类应该使用精确的声明实现此特殊方法。
ANY-ACCESS-MODIFIFER Object readResolve() throws ObjectStreamException;
这个readResolve方法遵循和writeReplace同样的调用和访问规则。
序列化过程运行时将每个可序列化的类与版本号关联,这个版本号称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否加载与该序列化兼容的该对象的类。如果接受方加载了一个和发送方有着不同的serialVersionUID的类,那么反序列化过程会抛出一个InvalidClassException的异常。可序列化类可以通过声明为“serialVersionUID”的字段显示声明其自己的serialVersionUID,字段必须时static,final并且时long类型:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化的类没有显示声明serialVersionUID,则序列化运行时将给予类的各个方面计算该类默认的serialVersionUID,Java桂发中对这个过程有所描述。但是,强烈建议所有可序列化类显示声明serialVersionUID,因为默认的serialVersionUID的计算对类的细节高度敏感,这些一节可能因为编译器不同而不同,因此在反序列化期间可导致意外的InvalidClassExceptions.因此,为了保证代码可以在不同的编译器下正常编译,应该显式声明serialVersionUID。强烈建议在声明serialVersionUID时使用private修饰符,因为此类声明仅适用于立即声明的class-serialVersion字段对继承的类并没有作用。数组类不能声明显式serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类不需要匹配serialVersionUID值。