java.io 源码学习01 Serializable接口

1 序列化描述: 

Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,
并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,
自动弥补不同操作系统之间的差异

 2. 两种主要特性:

一是Java的远程方法调用RMI,使存活于其他计算机上的对象使用起来就像是存活于本机一样。
二是对Java Beans来说,对象的序列化也是必需的,使用一个Bean时,
在设计阶段对它的状态信息进行配置,并保存这些信息,在程序启动时进行后期恢复。

3. 对象序列化为字节的过程:

创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,
调用writeObject()即可将对象序列化,并将其发送给OutputStream

4. 字节序列还原为对象的过程: 

创建某些InputStream对象,然后将其封装在一个ObjectInputStream对象内,
调用readObject()即可还原为对象,还原回来的对象是Object,需要类型转换
对象序列化时基于字节的,要使用字节输入流/输出流
对象序列化不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用

5. 源码

/**
  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 themselves
  serializable.  The serialization interface has no methods or fields
  and serves only to identify the semantics of being serializable.

  实现java.io.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. 为了允许序列化非可序列化类的子类型,子类型可以承担保存和恢复超类型的公共, 受保护和(如果可访问)包字段的状态的责任。 只有当它扩展的类具有可访问的no-arg构造函数来初始化类的状态时,子类型才可以承担此责任。 如果不是这种情况,则声明类Serializable是错误的。将在运行时检测到错误。

During deserialization, the fields 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. 在反序列化期间,将使用类的public或protected no-arg构造函数初始化非可序列化类的字段。 必须可以对可序列化的子类访问no-arg构造函数。可序列化子类的字段将从流中恢复。

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. 遍历图形时,可能会遇到不支持Serializable接口的对象。 在这种情况下,将抛出NotSerializableException,并将标识非可序列化对象的类。

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures: 在序列化和反序列化过程中需要特殊处理的类必须使用这些精确签名实现特殊方法:

  private void writeObject(java.io.ObjectOutputStream out)
      throws IOException
  private void readObject(java.io.ObjectInputStream in)
      throws 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 it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput. writeObject方法负责为其特定类编写对象的状态, 以便相应的readObject方法可以恢复它。 可以通过调用out.defaultWriteObject来调用保存Object字段的默认机制。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法或使用DataOutput 支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。

The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism 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 superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput. readObject方法负责从流中读取并恢复类字段。 它可以调用in.defaultReadObject来调用恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject方法使用流中的信息来指定流中保存的对象的字段以及当前对象中相应命名的字段。 这处理了类在演变为添加新字段时的情况。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法或使用DataOutput支持的原始数据类型的方法 将各个字段写入ObjectOutputStream来保存状态。

The readObjectNoData method is responsible for initializing 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 receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream. readObjectNoData方法负责在序列化流未将给定类列 为要反序列化的对象的超类的情况下初始化其特定类的对象的状态。 如果接收方使用与发送方不同版本的反序列化实例的类, 并且接收方的版本扩展了未由发送方版本扩展的类, 则可能发生这种情况。如果序列化流已被篡改, 也可能发生这种情况;因此,尽管存在“恶意”或不完整的源流, readObjectNoData仍可用于正确初始化反序列化对象。

Serializable classes that need to designate an alternative object 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 private, protected and package-private access. Subclass access to this method follows java accessibility rules. 如果方法存在,则可以通过序列化调用此writeReplace方法, 并且可以从要序列化的对象的类中定义的方法访问该方法。 因此,该方法可以具有私有,受保护和包私有访问。 对此方法的子类访问遵循java可访问性规则。

Classes that need to designate a replacement when an instance of it is read from the stream should implement this special method with the exact signature. 从流中读取实例时需要指定替换的类应该使用精确签名实现此特殊方法。

  ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
  

This readResolve method follows the same invocation rules and accessibility rules as writeReplace. 此readResolve方法遵循与writeReplace相同的调用规则和可访问性规则。

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, 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 {@link 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: 序列化运行时将每个可序列化类与版本号相关联, 称为serialVersionUID,在反序列化期间使用该版本号 来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。 如果接收者为具有与相应发送者类的serialVersionUID不同的对象加载了一个类, 则反序列化将导致{@link InvalidClassException}。 可序列化类可以通过声明名为“serialVersionUID” 的字段来显式声明其自己的serialVersionUID, 该字段必须是static,final和 long 类型:

  ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
  
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly 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 across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array 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. 如果可序列化类未显式声明serialVersionUID, 则序列化运行时将基于类的各个方面计算该类的默认serialVersionUID值, 如Java(TM)对象序列化规范中所述。但是,强烈建议 所有可序列化类都显式声明serialVersionUID值, 因为默认的serialVersionUID计算对类详细信息高度敏感, 可能因编译器实现而异,因此可能导致意外的反序列化期间的InvalidClassException 。 因此,为了保证跨不同java编译器实现的一致的serialVersionUID值, 可序列化类必须声明显式的serialVersionUID值。强烈建议显式serialVersionUID声明尽可能使用 private 修饰符,因为此类声明仅适用于立即声明的类 - serialVersionUID字段 作为继承成员无用。数组类不能声明显式的serialVersionUID, 因此它们始终具有默认的计算值,但是对于数组类,不需要匹配serialVersionUID值。 @author unascribed @see java.io.ObjectOutputStream @see java.io.ObjectInputStream @see java.io.ObjectOutput @see java.io.ObjectInput @see java.io.Externalizable @since JDK1.1 */ public interface Serializable { }

 

6. 示例演示序列化相关的一些细节问题

   a. 基础

    @Test
    public void test03() {
        String f = "src/main/resources/file/a.txt";
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
            Student student = new Student();
            student.setName("aaa");
            student.setAge(18);
            objectOutputStream.writeObject(student);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Test
    public void test04(){
        String f = "src/main/resources/file/a.txt";
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(f));
            Student student = (Student) objectInputStream.readObject();
            System.out.println(student);
        }catch (Exception e){
            e.printStackTrace();
        }
    //如果没有设置serialVersionUID的话,会抛出java.io.InvalidClassException 异常
    }

  b. 反序列化回来的对象和原对象内容一致,但是一个新对象

    @Test
    public void test05() throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;
        String f = "src/main/resources/file/a.txt";
        Student student = new Student();
        student.setName("aaa");
        student.setAge(18);
        try {
            objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
            objectOutputStream.writeObject(student);
            objectInputStream = new ObjectInputStream(new FileInputStream(f));
        } catch (IOException e) {
            e.printStackTrace();
        }
        Student student2 = (Student) objectInputStream.readObject();
        System.out.println(student == student2 ? "true" : "false");
    }

7. 一些方法

若希望类的不同版本对序列化兼容,需要确保类的不同版本具有相同的serialVersionUID; 

若不希望类的不同版本对序列化兼容,需要确保类的不同版本具有不同的serialVersionUID;

对象中存在引用对象时的序列化,其引用的对象也需要实现Serializable接口;

当父类实现了Serializable接口时,其子类不需要显示声明Serializable接口也可以序列化 
当父类未实现了Serializable接口,但子类实现了Serizlizable接口,子类序列化时会失败

static 关键字修饰变量,不受序列化反序列化影响,序列化和反序列化都是基于对象,但是static是类的任何实例对象共享的

transient 关键字 修饰的变量,也不受序列化反序列化影响,该变量的值反序列化时是该变量数据类型的默认值,相当于隐藏了原来的值

 

目前只用到这些概念,先写到这,当然还有其他。

https://blog.csdn.net/rickesy/article/details/56677283

 

你可能感兴趣的:(jdk源码,java源码)