一、序列化与反序列化的概念以及使用场景

1、概念

a)序列化:将对象转换成字节序列的过程;

b)反序列化:将字节序列恢复成对象的过程。

2、使用场景

1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2) 在网络上传送对象的字节序列。

二、结合例子说明

进行序列化的对象,需要实现Serializable接口,否则将无法序列化。序列化采用ObjectOutputStream的writeObject方法实现;反序列化则使用ObjectInputStream的readObject将字节序列还原成对象。

首先创建一个User类,只有简单的几个属性,如下:

import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
//重写了toString,方便打印观察
return "age:"+age+",name:"+name+",address:"+address;
}
}
User对象已经实现了序列化接口。接着进行测试,如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
/**
* 序列化
* @param filePath
* 序列化要写入的文件路径
* @throws Exception
*/
public static void writeObject(String filePath)throws Exception{
User u = new User();
u.setAddress("福建省厦门市");
u.setAge(18);
u.setName("漫天的沙");
ObjectOutputStream oos = null;
try{
oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(u);
oos.flush();
}finally{
if(oos != null){
oos.close();
}
}
}
/**
* 反序列化
* @param filePath
* 序列化的文件
* @throws Exception
*/
public static void readObject(String filePath) throws Exception{
ObjectInputStream ois = null;
try{
ois = new ObjectInputStream(new FileInputStream(filePath));
User u = (User)ois.readObject();
System.out.println(u);
}finally{
if(ois != null){
ois.close();
}
}
}
public static void main(String[] args) throws Exception{
String filePath = "f:/obj.out";
writeObject(filePath);
readObject(filePath);
}
}

执行后打印如下:

java序列化和反序列化 java序列化和反序列化过程_java反序列化入门

证明反序列化成功。

三、扩展

1、serialVersionUID

实现了Serializable接口的对象,如果没有显性的设置serialVersionUID,系统会自动根据方法/属性等计算序列化ID,如果显性进行了设置,则直接使用该值。serialVersionUID有什么用呢?我们可以试着对User对象进行显性设置serialVersionUID=1L,然后再将之前序列化的文件进行反序列化,如下:

/** 为用户对象显性设置序列化id */
private static final long serialVersionUID = 1L;

重新运行程序,报错如下:

Exception in thread "main" java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -5216935088914625952, local class serialVersionUID = 1
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at Test.readObject(Test.java:42)
at Test.main(Test.java:54)

意思即序列化文件的序列化ID与要反序列化成的对象序列化ID不兼容,因此反序列化失败。所以,如果序列化对象后续可能会增减字段或者修改,为了保证兼容,需要显性的设置序列化ID,避免修改之后原先的序列化不能成功的反序列化产生问题。

2、transient字段使用

如果字段不需要进行序列化,可以在修饰符后添加transient的声明,这样在序列化的时候属性将不参与序列化,例如上面的User对象name不参与序列化,则声明为

/** transient在对象初始化时可以让该字段不参与序列化 */
private transient String name;

重新运行程序,如下:

age:18,name:null,address:福建省厦门市

3、静态变量序列化

正常情况下,静态变量也可以正常的序列化。但是如果中间静态变量进行调整呢,序列化的对象中该静态变量是否会产生变化?为了验证效果,在上面的User新增一个静态变量,同时为了方便观察,重写toString方法,如下:

/**User对象新增的静态变量*/
public static int a = 35555;
@Override
public String toString() {
//重写了toString,方便打印观察
return "age:"+age+",name:"+name+",address:"+address+",a:"+a;
}
对main方法调整了,如下:
public static void main(String[] args) throws Exception{
String filePath = "f:/obj.out";
writeObject(filePath);
//修改User静态变量a的值
User.a = 10;
readObject(filePath);
}
重新运行,结果如下:
age:18,name:null,address:福建省厦门市,a:10

说明静态变量的值变了。分析原因,因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。