序列化机制详解
什么是Java序列化
程序运行时,可以在内存中创建Java对象实例,这些Java对象的生命周期会随JVM停止运行而消失,如果在JVM停止之后需要持久化内存中的Java对象,并随JVM重新运行时在内存中恢复这些Java对象,或者通过网络传输Java对象,这些场景都需要运用序列化。
序列化: 将Java对象转换成字节序列过程
反序列化: 将字节序列还原成Java对象过程

序列化常见应用场景
序列化有主要有两类用途:通过序列机制将Java对象持久化磁盘、让Java对象通过网络传输

常见应用场景
- 将对象序列化为字节流后,可以将字节流保存至磁盘文件或存储到数据库中,实现对象的持久化存储,当需要时,再将保存的对象进行反序列化,这样可以在程序重新启动或数据恢复时重新加载对象。
- 消息队列系统中,消息的发送之前需要将对象序列化为字节流,接收端再进行反序列化。
- 对象通过网络传输(远程RPC调用时)之前需要先将对象序列化,接收到字节序列后再进行反序列化。
Java序列化API
java.io.Serializable接口
Serializable接口是Java序列化的核心接口,接口中没有方法或字段。实现该接口的类可以将其对象转换为字节流进行序列化,并且可以从字节流中反序列化为对象。
public interface Serializable {
}
java.io.Externalizable接口
Externalizable接口是Serializable接口的子接口,接口中定义了两个抽象方法:writeExternal()和readExternal()两个方法,如果使用此接口实现序列化和反序列化,实现Externalizable接口的同时需要重写writeExternal()和readExternal()。
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
java.io.ObjectOutputStream类
ObjectOutputStream类表示对象输出流,该类的writeObject(Object obj)方法可以指定对象进行序列化,再将字节序列写入输出流中。
java.io.ObjectInputStream类
ObjectInputStream类表示对象输出流,该类的readObject()方法从输入流中读取字节序列,反序列化为一个对象。
序列化底层原理
Serializable是一个空接口,如果保证实现了该接口就可以进行对象的序列化和反序列化呢?
public interface Serializable {
}
通过文末例子制造java.io.NotSerializableException异常堆栈,从堆栈信息进入序列化源码:writeObject > writeObject0 > writeOrdinaryObject > writeSerialData > invokeWriteObject
Exception in thread "main" java.io.NotSerializableException:
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.rbd.chat.util.Person.main(Person.java:52)
writeObject0方法中代码:
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
ObjectOutputStream 在序列化时,会判断被序列化的对象属于String、array、enum、Serializable其中哪一种类型,如果都不是抛出NotSerializableException异常,如果对象的类实现Serializable接口则继续调用writeOrdinaryObject方法实现序列化的下一步操作,所以Serializable接口的实现作为一个序列化标志。
序列化使用
定义一个Person类并实现Serializable接口:
public class Person implements Serializable {
private String name;
private String sex;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
将对象序列化到文件中
通过ObjectOutputStream类中writeObject(Object obj)方法实现将对象输出到文件中:
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("/data/out.ser"));
Person person = new Person();
person.setName("张三");
person.setSex("男");
person.setAge(25);
outputStream.writeObject(person);
outputStream.flush();
outputStream.close();
输出的文件:

从文件中反序列化对象
通过ObjectInputStream类中readObject()方法反序列化对象:
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("/data/out.ser"));
Person person = (Person)inputStream.readObject();
inputStream.close();
System.out.println(person.toString());
运行结果:

序列化实践
serialVersionUID
反序列化时,会检查字节流中的serialVersionUID与本地类中的serialVersionUID进行比较,如果相同则认为是一致的执行反序列化,如果不同则认为序列化版本不一样会抛出InvalidCastException异常。
实现序列化接口的类中需要显式声明一个serialVersionUID变量,如果没有声明,序列化机制会自动生成一个serialVersionUID用于版本比较(没有声明的情况编译多次,serialVersionUID也不会改变,如果类做了变更,serialVersionUID也会相应的改变)

序列化对单例模式的影响
单例模式的目的是确保在JVM中只有一个实例对象的存在。但是如果将一个单例对象被序列化再反序列化时,会创建一个新的实例,破坏了单例的唯一性。这是因为序列化操作将对象保存到字节流中,而反序列化操作会从字节流中重新创建一个对象实例。为了保证单例的唯一性,可以在单例类中加入readResolve()方法,保证反序列化时返回相同对象。
1.以下创建一个实现序列化接口的Person单例类:
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private static Person instance;
private Person() {
// 私有构造函数
}
public static Person getInstance() {
if (instance == null) {
instance = new Person();
}
return instance;
}
}
2.实例化Person类并将序列化保存到本地,反序列化后与实例对象进行比较:
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
// 序列化单例对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
Person person = Person.getInstance();
out.writeObject(person);
out.close();
// 反序列化单例对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person deserializedPerson = (Person) in.readObject();
in.close();
// 判断是否为同一个实例
System.out.println("是否为同一个实例对象: " +
(person == deserializedPerson));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//返回结果:是否为同一个实例对象: false
3.在Person类中加入readResolve()方法再进对象比较:
private Object readResolve() throws ObjectStreamException {
return instance;
}
//返回结果:是否为同一个实例对象: true
transient 与 static 关键字修饰字段不会被序列化
transient: 变量修饰符,使用它修饰变量时,可以阻止变量的序列化,反序列化,transient修饰的变量不会被恢复。transient只能修饰变量,不能修饰类与方法。
static: 序列化是针对对象的,而不是类。静态变量与类是关联的,在类加载初始化时,在类的整个生命周期保持相同的值,所以,不需要将静态变量序列化到字节流中。
serialVersionUID 被 static 关键字修饰,为何会被序列化?
serialVersionUID是一个特殊的静态变量,不会被序列化和保存到字节流中,serialVersionUID变量只会用于JVM识别。
子类实现序列化,父类没有实现序列化,父类字段值丢失
子类接口实现了Serializable接口,父类接口没有实现,代表父类不会被序列化,如果需要父类属性的值不被丢失,父类也需要实现Serializable接口。
//定义没有实现Serializable接口父类
public class Person implements Serializable {
private String personId;
}
//定义实现Serializable接口的子类
public class Male extends Person implements Serializable {
private String name;
private Integer age;
}
//将子类实例化设置属性值并序列持久化到文件,并反序列化对象输出值
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("person.ser"));
Male male = new Male();
male.setPersonId("109283X");
male.setName("张三");
male.setAge(25);
outputStream.writeObject(male);
outputStream.flush();
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("person.ser"));
Male male1 = (Male) inputStream.readObject();
inputStream.close();
System.out.println("序列化前personID:" + male.getPersonId());
System.out.println("反序列化后personID:" + male1.getPersonId());
//执行结果:
//序列化前personID:109283X
//反序列化后personID:null
序列化类中字段类型为对象,则该对象的类必须序列化
如果类里面含有引用类型的成员变量需要进行序列化时,引用类型的类(引用类型的类继承的父类或实现的接口实现了Serializable接口也满足)也必须实现Serializable接口,否则序列化时会抛出NotSerializableException异常。
public class Person implements Serializable{
private String personId;
public Male male;
}
//引用类型的类不实现 Serializable接口
public class Male{
}
//执行序列化会抛出 java.io.NotSerializableException异常
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("person.ser"));
Person person = new Person();
person.setPersonId("109283X");
person.setMale(new Male());