当前位置: 首页 > Thinking in Java > 正文

第 18 章 – java IO系统 – 序列化与反序列化

1. 反序列化时必须在classpath中能找到被序列化的类的class文件.

 

2. Serializable接口 和 transient关键字

   只有实现了Serializable接口的类的实例才能序列化. 

   这种类型的实例序列化时只存储当前对象的所有字段的二进制值.

   不会调用对象的默认构造器,所有对象的默认构造器可以不必是public的.

 

   使用transient关键字修饰的字段,在序列化时不会保存字段的值.

   transient关键字修饰的字段在反序列化时出来的值是未初始化的(数字0,或null之类的).

   示例代码:

package io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class ClsWithTransient implements Serializable
{
  private int             i = 0;
  transient public String s = "初始值";
  transient private int iTransient = 111;

  public void setS(String s)
  {
    this.s = s;
  }
  
  public String toString()
  {
    return String.format("ClsWithTransient 类: i=%d,transient s=%s,transient iTransient=%d",i,s,iTransient);
  }
}

public class TestTransient
{
  public static void main(String[] args) throws Exception
  {
    ClsWithTransient cls = new ClsWithTransient();
    System.out.println("刚创建的对象 " + cls);
    
    String s = "修改的值";
    System.out.println("修改对象的字符串值为:" + s);
    cls.setS(s);
    System.out.println("修改完成,序列化之对象前 " + cls);
    
    ObjectOutputStream obout = new ObjectOutputStream(new FileOutputStream(new File("D:\\D\\transient_obj.out")));
    obout.writeObject(cls);
    obout.flush();
    obout.close();
    System.out.println("序列化写入完毕!");
    
    System.out.println("\n开始反序列化...");
    ObjectInputStream obin = new ObjectInputStream(new FileInputStream(new File("D:\\D\\transient_obj.out")));
    ClsWithTransient aCls = (ClsWithTransient) obin.readObject();
    obin.close();
    System.out.println("反序列化出来的对象为 " + aCls);
    //反序列化出来的对象为 ClsWithTransient 类: i=0,transient s=null,transient iTransient=0
    //表明transient修饰的字段在反序列化时是未初始化的.
    
  }
  /** 输出如下:
   * 
刚创建的对象 ClsWithTransient 类: i=0,transient s=初始值,transient iTransient=111
修改对象的字符串值为:修改的值
修改完成,序列化之对象前 ClsWithTransient 类: i=0,transient s=修改的值,transient iTransient=111
序列化写入完毕!

开始反序列化...
反序列化出来的对象为 ClsWithTransient 类: i=0,transient s=null,transient iTransient=0

   */
}

 

3. Externalizable 接口集成自 Serializable接口  

   具有两个方法:

   void readExternal(ObjectInput in) 

          对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 

          的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。 

   void writeExternal(ObjectOutput out) 

          该对象可实现 writeExternal 方法来保存其内容,

          它可以通过调用 DataOutput 的方法来保存其基本值,

          或调用 ObjectOutput 的 writeObject 方法来保存对象、

          字符串和数组。 

   这两个方法由系统调用.

   实现了Externalizable接口的对象在序列化时,先调用writeExternal(ObjectOutput out) 这个方法.

   传递的参数就是 用来写入序列化的ObjectOutputStream 对象,并将该对象传递给

   writeExternal方法.ObjectOutputStream对象实现了ObjectOutput接口(是DataOutput的子接口),

   该接口有各种类似writeInt等方法,还有writeObject方法可以在序列化时写入想要的保存的信息.

 

   readExternal(ObjectInput in)在反序列化时由系统自动调用.传入当前用来读取序列化文件的

   ObjectInputStream来读取序列化时writeExternal(ObjectOutput out)中写入的各种信息,并且

   用这些信息来初始化正在反序列化的对象.ObjectInputStream对象实现了ObjectInput接口(继承

   自DataInput接口,具有各种类似readInt等方法,还有readObject方法).

 

   在反序列化 Externalizable 对象时的所做的操作如下:

   (1) 先初始化字段,注意,transient 修饰的字段也会初始化

   (2) 如果有初始化块,则执行初始化块(不调用static修饰的静态初始化块)

   (3) 调用无参数的公共构造器(默认构造器)创建一个实例.

       因此,序列化的对象,必须要有public的默认构造器(无参构造器).

   (4) 调用 readExternal 方法进行初始化:通过从 ObjectInputStream 中读取 Serializable 对象

       可以恢复这些对象。

 

   所以,Externalizable序列化时是不会记录该对象的字段的,因为反序列化是依靠对象的默认构造器

   和readExternal(ObjectInput in)方法.

 

   比如下面的例子里面,class Blip2 implements Externalizable 没有public的默认构造器

   虽然在序列化时可以成功,但是反序列化时报异常:

   java.io.InvalidClassException: io.Blip2; io.Blip2; no valid constructor

 

   示例代码:

package io;

//: io/Blips.java
// Simple use of Externalizable & a pitfall.
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

class Blip1 implements Externalizable
{
  //反序列化时会依次初始化下面的字段
  private int i = 111;
  private String s ="初始的值";
  transient private String sTr ="transient初始的值"; 
  //实现Externalizable的对象中的transient字段依然在反序列化时初始化
  
  private static int iStatic = 222;
  
  static //反序列化时不调用static修饰的静态初始化块
  {
    System.out.println("静态 初始化块中的初始化");
    iStatic = 333;
  }
  
  //反序列化时,在初始化完字段后,调用初始化快
  {
    System.out.println("初始化块中的初始化");
  }
  
  
  public Blip1()
  {
    System.out.println("Blip1 Constructor");
  }

  public void writeExternal(ObjectOutput out) throws IOException
  {
    System.out.println("Blip1.writeExternal");
    
    out.writeInt(123);
    out.writeObject("s在writeExternal中保存的值");
    out.writeObject("transient 修饰的sTr 在writeExternal中保存的值");
  }

  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
  {
    System.out.println("Blip1.readExternal");
    i = in.readInt();
    s = (String) in.readObject();
    //sTr = (String) in.readObject();
  }
  
  public String toString()
  {
    return String.format("i=%d,s=%s,sTr=%s",i,s,sTr);
  }
}

class Blip2 implements Externalizable
{
  Blip2() //这个构造器不是public,序列化可以正常进行,反序列化时报异常.
  {
    System.out.println("Blip2 Constructor");
  }

  public void writeExternal(ObjectOutput out) throws IOException
  {
    System.out.println("Blip2.writeExternal");
  }

  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
  {
    System.out.println("Blip2.readExternal");
  }
}


public class Blips
{
  public static void main(String[] args) throws IOException, ClassNotFoundException
  {
    System.out.println("Constructing objects:");
    Blip1 b1 = new Blip1();
    Blip2 b2 = new Blip2();
    
    ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("D:\\D\\Blips.out"));
    System.out.println("\nSaving objects:");
    o.writeObject(b1);
    o.writeObject(b2);
    o.close();
    // Now get them back:
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\D\\Blips.out"));
    System.out.println("\nRecovering b1:");
    b1 = (Blip1) in.readObject();
    System.out.println(b1);
    //由这里的输出可知 
    //(1) 先输出 "初始化块中的初始化" 表示 反序列化时先调用初始化块
    //(2) 输出Blip1 Constructor 表示 反序列化时要调用 默认public 构造器
    //(3) Blip1.readExternal 表示调用了readExternal 方法
    //(4) 'i=123,s=s在writeExternal中保存的值,sTr=transient初始的值' 中i值是在readExternal中恢复的
    //    s字段 值也是在readExternal恢复的,而 sTr字段 依然保存了字段初始化时的值,说明实现了Externalizable接口
    //    的对象在反序列化时会先根据class的定义初始化对象的字段,并且transient修饰的字段也会初始化
    
     
    // ! System.out.println("Recovering b2:");
    //b2 = (Blip2)in.readObject(); 
    //去掉上面注释,执行时会报异常java.io.InvalidClassException: io.Blip2; io.Blip2; no valid constructor
  }
}
/**
 * 输出为:
 * Constructing objects:
静态 初始化块中的初始化
初始化块中的初始化
Blip1 Constructor
Blip2 Constructor

Saving objects:
Blip1.writeExternal
Blip2.writeExternal

Recovering b1:
初始化块中的初始化
Blip1 Constructor
Blip1.readExternal
i=123,s=s在writeExternal中保存的值,sTr=transient初始的值
 * 
 */

 

赞 赏

   微信赞赏  支付宝赞赏


本文固定链接: https://www.jack-yin.com/coding/thinking-in-java/2053.html | 边城网事

该日志由 边城网事 于2015年03月02日发表在 Thinking in Java 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 第 18 章 – java IO系统 – 序列化与反序列化 | 边城网事

第 18 章 – java IO系统 – 序列化与反序列化 暂无评论

发表评论

快捷键:Ctrl+Enter