代码review的时候经常看到部分老代码的实体类莫名其妙的实现了Serializable接口,也问不出个所以然来,于是抽时间系统的学一下Serializable接口。
序列化的概念
序列化是指将对象的状态信息转换成可以存储或可以传输的形式的过程,本文特指将对象转换成二进制数据的过程。
序列化的应用场景
1.tomcat关闭的时候会把session对象序列化到SESSION.ser文件中,再次启动的时候就把这些session加载到内存中来
2.网络之间对象数据传输,RMI技术就是基于java序列化和反序列化
Serializable接口官方解释
1.Serializable接口是一个标记接口,标记着实现了该接口的类以及子类可以被序列化
2.序列化一个没有实现Serializable接口对象的时候会报错NotSerializableException
3.需要自定义序列化过程的时候需要在待序列化对象中实现writeObject()或readObject()方法
4.序列化运行时使用一个称为serialVersionUID的版本号与每个可序列化对象相关联,如果接收者加载的该对象的类的serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。建议序列化对象声明一个确定的serialVersionUID的值。
Serializable接口使用示例
普通序列化
将一个对象序列化到本地,并反序列化出来解析。
定义一个Student类:
package com.victor.technology.serialzable;
import java.io.Serializable;
public class Student implements Serializable
{
private static final long serialVersionUID = 1L;
private String name;
private int number;
public Student(String name, int number)
{
super();
this.name = name;
this.number = number;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getNumber()
{
return number;
}
public void setNumber(int number)
{
this.number = number;
}
}
定义一个序列化测试类,将Student对象序列化并保存到本地文件:
package com.victor.technology.serialzable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializableTest
{
public static void main(String[] args)
{
String fileName = "E:\\student.txt";
Student student = new Student("victor", 1);
try (ObjectOutputStream objectOutput = new ObjectOutputStream(new FileOutputStream(fileName)))
{
objectOutput.writeObject(student);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
执行之后打开student.txt内容如下:
定义一个反序列化测试类,解析Student对象:
package com.victor.technology.serialzable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializableTest
{
public static void main(String[] args)
{
String fileName = "E:\\student.txt";
try (ObjectInputStream objectInput = new ObjectInputStream(new FileInputStream(fileName)))
{
Student student = (Student) objectInput.readObject();
System.out.println("Name: " + student.getName());
System.out.println("Number: " + student.getNumber());
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
执行结果如下:
Name: victor
Number: 1
静态变量序列化
在Student中添加一个静态变量teacher,序列化之后反序列化之前修改teacher的值,发现反序列化得到的teacher的值是修改之后的:
序列化代码如下:
package com.victor.technology.serialzable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializableTest
{
public static void main(String[] args)
{
String fileName = "E:\\student.txt";
Student student = new Student("victor", 1, "Mr Wang");
try (ObjectOutputStream objectOutput = new ObjectOutputStream(new FileOutputStream(fileName)))
{
objectOutput.writeObject(student);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
反序列化代码如下:
package com.victor.technology.serialzable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializableTest
{
public static void main(String[] args)
{
String fileName = "E:\\student.txt";
try (ObjectInputStream objectInput = new ObjectInputStream(new FileInputStream(fileName)))
{
Student.setTeacher("Mr Yang");
Student student = (Student) objectInput.readObject();
System.out.println("Name: " + student.getName());
System.out.println("Number: " + student.getNumber());
System.out.println("Teacher: " + student.getTeacher());
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
反序列化代码执行结果如下:
Name: victor
Number: 1
Teacher: Mr Yang
transient关键字修饰的变量序列化
经常在实现了Serializable接口的类中能看见transient关键字,transient关键字的作用是:阻止实例实例中那些用此关键字声明的变量持久化,反序列化的时候这样的实例变量值不会被持久化和恢复。
将Student类中的teacher的属性由static改成transient,然后进行序列化和反序列化
反序列化代码执行结果如下:
Name: victor
Number: 1
Teacher: null
可见,被transient声明的属性并不会被序列化和反序列化,所以transient一般用于声明敏感数据。
serialVersionUID属性
待Student对象序列化之后将serialVersionUID属性改成2L,然后执行反序列化,执行结果如下:
java.io.InvalidClassException: com.victor.technology.serialzable.Student; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.victor.technology.serialzable.DeserializableTest.main(DeserializableTest.java:17)
可见serialVersionUID前后不一致会导致InvalidClassException,所以需要序列化对象的serialVersionUID最好设置默认值1L并不要改动。
学完之后,结合现在的工作,感觉基本用不到序列化,现在的项目前后台之间和后台服务之间传递数据的时候都是用现成的第三方类库如jackson、json-lib进行实体和JSON字符串之间的转化。解析的时候也可以把JSON字符串转换成Map,只获取指定的值,更灵活方便。