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接口的类启用了类的可序列化。
  未实现此接口的类将不会将其任何状态序列化或反序列化。
  可序列化类的所有子类型本身都是可序列化的。
  序列化接口没有方法或字段,仅用于标识可序列化的语义。

 <p>

  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是错误的。将在运行时检测到错误。

 <p>

  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构造函数。可序列化子类的字段将从流中恢复。

 <p>

  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,并将标识非可序列化对象的类。

 <p>

  Classes that require special handling during the serialization and
  deserialization process must implement special methods with these exact
  signatures:

 在序列化和反序列化过程中需要特殊处理的类必须使用这些精确签名实现特殊方法:

  <PRE>
  private void writeObject(java.io.ObjectOutputStream out)
      throws IOException
  private void readObject(java.io.ObjectInputStream in)
      throws IOException, ClassNotFoundException;
  private void readObjectNoData()
      throws ObjectStreamException;
  </PRE>

  <p>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来保存状态。


  <p>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来保存状态。

  <p>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仍可用于正确初始化反序列化对象。


  <p>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:

 需要指定在将对象写入流时使用的备用对象的可序列化类应该使用确切的签名实现此特殊方法:

  <PRE>
  ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
  </PRE><p>

  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可访问性规则。
 <p>

  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.

 从流中读取实例时需要指定替换的类应该使用精确签名实现此特殊方法。

  <PRE>
  ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
  </PRE><p>

  This readResolve method follows the same invocation rules and
  accessibility rules as writeReplace.

 此readResolve方法遵循与writeReplace相同的调用规则和可访问性规则。
 <p>

  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 <code>"serialVersionUID"</code> that must be static,
  final, and of type <code>long</code>:

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


  <PRE>
  ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
  </PRE>

  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 <em>strongly
  recommended</em> 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
  <code>InvalidClassException</code>s 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 <code>private</code> 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)对象序列化规范中所述。但是,<em>强烈建议</ em>
 所有可序列化类都显式声明serialVersionUID值,
 因为默认的serialVersionUID计算对类详细信息高度敏感,
 可能因编译器实现而异,因此可能导致意外的<code>反序列化期间的InvalidClassException </ code>。
 因此,为了保证跨不同java编译器实现的一致的serialVersionUID值,
 可序列化类必须声明显式的serialVersionUID值。强烈建议显式serialVersionUID声明尽可能使用<code>
 private </ code>修饰符,因为此类声明仅适用于立即声明的类 -  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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值