前言:在 Java 中,对象的反序列化操作十分便捷,只需让类实现特定接口即可完成序列化与反序列化过程。常见的做法是实现
java.io.Serializable(内部序列化)
或
java.io.Externalizable(外部序列化)
接口。值得注意的是,
java.io.Externalizable
接口本质上是对
java.io.Serializable
接口的一种扩展形式。
1. 对象的序列化与反序列化(两种实现方式)
(1)基于 java.io.Serializable 的序列化机制
该方式为最常用的序列化手段,具体步骤如下:
- 使目标类实现 Serializable 接口;
- 声明并设置
serialVersionUID
- 利用 ObjectOutputStream 执行对象序列化;
- 通过 ObjectInputStream 完成对象反序列化。
Text 类示例代码:
public class Text1 implements Serializable {
@Serial
private static final long serialVersionUID = 3842394723984792L;
public String text = "111";
public int number;
public Text1() {
}
public void text1() {
System.out.println("我是text1中的方法");
}
@Override
public String toString() {
return "Text1{" +
"text='" + text + '\'' +
", number=" + number +
'}';
}
}
Main 类中执行序列化与反序列化的代码:
// 建立文件字节输出流
FileOutputStream out = new FileOutputStream("1.txt");
// 包装为对象输出流
ObjectOutputStream oos = new ObjectOutputStream(out);
// 将对象写入文件
oos.writeObject(new Text1());
// 关闭流资源
oos.close();
// 建立文件字节输入流
FileInputStream fis = new FileInputStream("1.txt");
// 包装为对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取反序列化后的对象
Text1 o = (Text1) ois.readObject();
// 关闭流资源
ois.close();
// 输出对象内容
System.out.println(o);
(2)采用 java.io.Externalizable 接口实现序列化
此方法提供了更精细的控制能力,需手动定义序列化逻辑,步骤包括:
- 让类实现 Externalizable 接口;
- 重写接口中规定的两个核心方法;
- 设定
serialVersionUID
- 使用 ObjectOutputStream 进行序列化操作;
- 借助 ObjectInputStream 实现反序列化处理。
Text 类实现 Externalizable 示例:
public class Text1 implements Externalizable {
@Serial
private static final long serialVersionUID = 3842394723984792L;
public String name = "张三";
public int number = 888888;
public Text1() {
}
public void text1() {
System.out.println("我是text1中的方法");
}
@Override
public String toString() {
return "Text1{" +
"name='" + name + '\'' +
", number=" + number +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(number);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
number = in.readInt();
}
}
Main 类中的调用逻辑保持一致:
// 建立文件字节输出流
FileOutputStream out = new FileOutputStream("1.txt");
// 转换为对象输出流
ObjectOutputStream oos = new ObjectOutputStream(out);
// 写入对象实例
oos.writeObject(new Text1());
// 关闭输出流
oos.close();
// 建立文件字节输入流
FileInputStream fis = new FileInputStream("1.txt");
// 转换为对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取对象
serialVersionUID
// 先建立字节流连接
FileOutputStream out = new FileOutputStream("1.txt");
// 转换为对象输出流
ObjectOutputStream oos = new ObjectOutputStream(out);
// 写入对象实例
oos.writeObject(new Text1());
// 关闭流资源
oos.close();
// 建立文件输入流连接
FileInputStream fis = new FileInputStream("1.txt");
// 包装成对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取反序列化后的对象
Text1 o = (Text1)ois.readObject();
// 释放流资源
ois.close();
// 输出对象内容
System.out.println(o);
自定义序列化与反序列化(writeObject 与 readObject)
当对某个类执行序列化操作时,Java 会通过反射机制自动调用该类中定义的特定方法。在序列化过程中,系统将查找并调用 writeObject 方法;而在反序列化时,则会自动触发 readObject 方法的执行。
因此,我们可以在需要进行序列化的类中手动添加这两个私有方法,以实现对序列化过程的精细化控制。需要注意的是,这些方法必须声明为 private,且其签名需严格符合规范,否则不会被 JVM 自动识别和调用。
DeserializationTest
writeObject(ObjectOutputStream oos)
readObject(ObjectInputStream)
readObject
writeObject
private,如果该类没有重写,默认调用默认的序列化方法
应用场景包括:
- 处理被
transient 修饰但仍需保存的字段
- 在序列化前对敏感数据进行加密,反序列化时再解密
- 应对更复杂的序列化逻辑需求,如版本兼容、数据校验等
示例代码:Text1 类
public class Text1 implements Serializable {
@Serial
private static final long serialVersionUID = 3842394723984792L;
transient public String name = "张三";
public int number = 888888;
public Text1() {
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 处理非 transient 字段
out.writeObject(name); // 手动写入 transient 字段
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 恢复非 transient 字段
name = (String) in.readObject(); // 手动恢复 transient 字段
}
@Override
public String toString() {
return "Text1{" +
"name='" + name + '\'' +
", number=" + number +
'}';
}
}
利用反序列化绕过构造函数创建对象
一个值得注意的特性是:**反序列化过程并不会调用类的构造方法**。这是因为 JVM 在反序列化时使用了底层机制,通过反射包中的特殊工厂生成实例。具体来说,它利用了 sun.reflect.ReflectionFactory 创建了一个专用于反序列化的“无构造调用”实例生成器。
这种方式与之前章节中提到的通过 Unsafe.allocateInstance() 直接分配内存创建对象类似,都可以实现跳过构造函数的目的。
sun.reflect.ReflectionFactory.newConstructorForSerialization
Constructor(反射构造方法对象)
Constructor
sun.misc.Unsafe
allocateInstance
实现步骤如下:
- 获取
ReflectionFactory 实例
- 通过工厂方法生成目标类的序列化专用构造器
- 使用该构造器创建对象实例
// 获取 ReflectionFactory 实例
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
// 获取 Text1 类用于反序列化的构造器(继承自 Object 的构造器)
Constructor<?> constructor = reflectionFactory.newConstructorForSerialization(
Text1.class,
Object.class.getConstructor()
);
// 通过 newInstance 实例化对象(不调用 Text1 的构造函数)
Text1 o = (Text1) constructor.newInstance();
// 打印结果
System.out.println(o);