Java对象序列化归纳整理

对象序列化主要是为了支持两种Java特性:

  1. 远程方法调用:向远程对象发送消息,通过对象序列化传递参数和返回值
  2. 支持JavaBean:在使用Bean配置其状态信息,将该对象序列化,在后期使用时由程序去恢复。

1. 默认序列化

对于一般的序列化而言,需要类及类中的数据域类型均实现Serializable接口即可。Serializable接口不包含任何方法,仅仅起标记作用。

  1. 序列化过程为:
    创建ObjectOutputStream对象,调用writeObject(Object obj)方法将对象序列化到输出列表中
  2. 反序列化过程为:
    创建ObjectInputStream对象,调用readObject()方法将数据还原为对象。

序列化会自动处理数据及对象之间的依赖关系。但是一般而言,还原之后的对象与原有对象地址不会相同。从输出结果中我们可以看到,在对一个Serializable对象进行还原时,没有调用任何构造器,包括默认构造器。

import java.io.*;
import java.util.*;
public class sequenceTest implements Serializable
{
 private String name;
 private double salary;
 public sequenceTest(String name,double salary)
 {
  this.name=name;
  this.salary=salary;
  System.out.println("constructor");
 }
 public sequenceTest()
 {
 System.out.println("default constructor");
 }
 public String toString()
 {
  return this.name+":"+this.salary;
 }
 public static void main(String[] args)
 {
  sequenceTest[] test={new sequenceTest("zhangsan",1000),new sequenceTest("lisi",1000),new sequenceTest("wangwu",1000)};
  System.out.println(test);
  System.out.println(Arrays.toString(test));
  try
  {
   ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("output.dat"));
   out.writeObject(test);
   out.close();
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  try
  {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream("output.dat"));
   sequenceTest[] tmp=(sequenceTest[])in.readObject();
   in.close();
   System.out.println(tmp);
   System.out.println(Arrays.toString(tmp));
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  catch(ClassNotFoundException e)
  {
   e.printStackTrace();
  }
 }
}
/*output
constructor
constructor
constructor
[LFw2Tools.sequenceTest;@70dea4e
[zhangsan:1000.0, lisi:1000.0, wangwu:1000.0]
[LFw2Tools.sequenceTest;@58372a00
[zhangsan:1000.0, lisi:1000.0, wangwu:1000.0]
*/

2. 可控序列化

可控序列化有两种方式。一种为实现Serializable接口,一种为实现Externalizable接口.

2.1 Serializable

2.1.1 transient

我们可以用关键字transient修饰数据域,来告知虚拟机在序列化对象时跳过序列化该数据域。如下所示:我们用transient修饰数据域salary,对象序列化时会跳过该数据域,还原时,该数据域被初始化为0.

import java.io.*;
import java.util.*;

public class sequenceTest implements Serializable
{
 private String name;
 private transient double salary;
 public sequenceTest(String name,double salary)
 {
  this.name=name;
  this.salary=salary;
 }
 public String toString()
 {
  return this.name+":"+this.salary;
 }
 public static void main(String[] args)
 {
  sequenceTest[] test={new sequenceTest("zhangsan",1000),new sequenceTest("lisi",1000),new sequenceTest("wangwu",1000)};
  System.out.println(test);
  System.out.println(Arrays.toString(test));
  try
  {
   ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("output.dat"));
   out.writeObject(test);
   out.close();
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  try
  {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream("output.dat"));
   sequenceTest[] tmp=(sequenceTest[])in.readObject();
   in.close();
   System.out.println(tmp);
   System.out.println(Arrays.toString(tmp));
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  catch(ClassNotFoundException e)
  {
   e.printStackTrace();
  }
 }
}
/*output
[LFw2Tools.sequenceTest;@70dea4e
[zhangsan:1000.0, lisi:1000.0, wangwu:1000.0]
[LFw2Tools.sequenceTest;@58372a00
[zhangsan:0.0, lisi:0.0, wangwu:0.0]
*/

2.1.2 自定义序列化方式

通过在类中提供方法:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws IOException;
这样一来当我们调用writeObject()和readObject()去序列化和还原对象时,会按照我们提供的方式进行。

import java.io.*;
import java.util.*;
public class sequenceTest implements Serializable
{
 private String name;
 private double salary;
 private void writeObject(ObjectOutputStream out) throws IOException
 {
  out.writeObject(name);//仅仅序列化域name
 }
 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
 {
  this.name=(String)in.readObject();
 }
 public sequenceTest(String name,double salary)
 {
  this.name=name;
  this.salary=salary;
 }
 public String toString()
 {
  return this.name+":"+this.salary;
 }
 public static void main(String[] args)
 {
  sequenceTest[] test={new sequenceTest("zhangsan",1000),new sequenceTest("lisi",1000),new sequenceTest("wangwu",1000)};
  System.out.println(test);
  System.out.println(Arrays.toString(test));
  try
  {
   ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("output.dat"));
   out.writeObject(test);
   out.close();
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  try
  {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream("output.dat"));
   sequenceTest[] tmp=(sequenceTest[])in.readObject();
   in.close();
   System.out.println(tmp);
   System.out.println(Arrays.toString(tmp));
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  catch(ClassNotFoundException e)
  {
   e.printStackTrace();
  }
 }
}
/*output
[LFw2Tools.sequenceTest;@70dea4e
[zhangsan:1000.0, lisi:1000.0, wangwu:1000.0]
[LFw2Tools.sequenceTest;@7699a589
[zhangsan:0.0, lisi:0.0, wangwu:0.0]
*/

上述程序中,我们仅仅在writeObject()和readObject()方法中对域name进行了序列化和还原。由输出结果可知,域salary没有参与序列化,还原时由Java虚拟机赋予默认值。

2.2 Externalizable

实现Externalizable接口,需要提供如下两个方法:

  1. public void writeExternal(ObjectOutput out) throws IOException;
  2. public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException;
    方法内部实现与2.1.2的方式相同,如下所示:
import java.io.*;
import java.util.*;
public class sequenceTest implements Externalizable
{
 private String name;
 private double salary;
 public void writeExternal(ObjectOutput out) throws IOException
 {
  out.writeObject(name);
 }
 public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
 {
  this.name=(String)in.readObject();
 }
 public sequenceTest(String name,double salary)
 {
  this.name=name;
  this.salary=salary;
 }
 public sequenceTest()
 {
 System.out.println("default constructor");
 }
 public String toString()
 {
  return this.name+":"+this.salary;
 }
 public static void main(String[] args)
 {
  sequenceTest[] test={new sequenceTest("zhangsan",1000),new sequenceTest("lisi",1000),new sequenceTest("wangwu",1000)};
  System.out.println(test);
  System.out.println(Arrays.toString(test));
  try
  {
   ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("output.dat"));
   out.writeObject(test);
   out.close();
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  try
  {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream("output.dat"));
   sequenceTest[] tmp=(sequenceTest[])in.readObject();
   in.close();
   System.out.println(tmp);
   System.out.println(Arrays.toString(tmp));
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  catch(ClassNotFoundException e)
  {
   e.printStackTrace();
  }
 }
}
/*output
[LFw2Tools.sequenceTest;@70dea4e
[zhangsan:1000.0, lisi:1000.0, wangwu:1000.0]
default constructor
default constructor
default constructor
[LFw2Tools.sequenceTest;@7ba4f24f
[zhangsan:0.0, lisi:0.0, wangwu:0.0]
*/

从输出中我们可以看到还原Externalizable对象时,会调用默认构造器。故需要注意以下几点:

  1. 类中要有可访问的默认构造器
  2. 方法名为writeExternal与readExternal
  3. 方法参数类型为ObjectOutput和ObjectInput

3. 对象序列化与版本控制

在序列化对象时,Java虚拟机会由类的信息生成一个序列号serialVersionUID,将该serialVersionUID与序列化信息相关联。在还原序列化对象时,Java虚拟机会将当前类的serialVersionUID信息与原有类的serialVersionUID信息相比较,一致才可以利用原有序列化信息对序列化对象进行还原。
需要注意的是,所有对类的修改均会改变当前类的serialVersionUID信息,由此导致反序列化还原报错“java.io.InvalidClassException: Fw2Tools.sequenceTest; local class incompatible: stream classdesc serialVersionUID = 7836654725641771422, local class serialVersionUID = -1878829023552068523“。
解决的办法为,在类文件中添加静态数据成员:
public static final long serialVersionUID,这样一来虚拟机会使用该静态数据成员的值而不是计算的值。

package Fw2Tools;
import java.io.*;
import java.util.*;
public class sequenceTest implements Externalizable
{
 public static final long serialVersionUID=7836654725641771422L;
 private String name;
 private double salary;
 public void writeExternal(ObjectOutput out) throws IOException
 {
  out.writeObject(name);
 }
 public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
 {
  this.name=(String)in.readObject();
 }
 public sequenceTest()
 {
  System.out.println("default constructor");
 }
 public sequenceTest(String name,double salary)
 {
  this.name=name;
  this.salary=salary;
 }
 public String toString()
 {
  return this.name+":"+this.salary;
 }
 public static void main(String[] args)
 {
  sequenceTest[] test={new sequenceTest("zhangsan",1000),new sequenceTest("lisi",1000),new sequenceTest("wangwu",1000)};
  System.out.println(test);
  System.out.println(Arrays.toString(test));
  {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream("output.dat"));
   sequenceTest[] tmp=(sequenceTest[])in.readObject();
   in.close();
   System.out.println(tmp);
   System.out.println(Arrays.toString(tmp));
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  catch(ClassNotFoundException e)
  {
   e.printStackTrace();
  }
 }
}

获取这个类较早版本serialVersionUID的方法为,在控制台窗口中运行如下指令:

serialVer Fw2Tools.sequenceTest
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值