Java中的对象序列化与反序列化机制详解
字数 1232 2025-11-25 07:18:01
Java中的对象序列化与反序列化机制详解
对象序列化是将Java对象转换为字节流的过程,以便存储或传输;反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存和持久化存储中广泛应用。下面逐步详解其核心机制、注意事项和实现原理。
一、序列化的基本用法
-
实现Serializable接口
- 若要使对象可序列化,需让类实现
java.io.Serializable接口。该接口是一个标记接口(无方法),仅用于标识对象可被序列化。 - 示例:
public class User implements Serializable { private String name; private int age; // 必须提供无参构造器(反序列化时反射调用) public User() {} // getter/setter省略 }
- 若要使对象可序列化,需让类实现
-
序列化与反序列化代码示例
- 使用
ObjectOutputStream和ObjectInputStream:// 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) { User user = new User("Alice", 30); oos.writeObject(user); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) { User restoredUser = (User) ois.readObject(); System.out.println(restoredUser.getName()); // 输出"Alice" }
- 使用
二、序列化机制的核心原理
-
序列化流程
- 当调用
writeObject()时,JVM会检查对象是否实现了Serializable接口。 - 递归序列化对象的所有成员变量(包括引用类型),但静态变量和 transient 修饰的变量不会被序列化。
- 每个对象关联一个序列化编号(serialVersionUID),用于反序列化时验证版本一致性。
- 当调用
-
serialVersionUID的作用
- 若未显式定义,JVM会根据类结构自动生成一个UID。一旦类结构变更(如增删字段),自动生成的UID会改变,导致反序列化失败(抛出
InvalidClassException)。 - 显式定义UID可避免该问题:
private static final long serialVersionUID = 1L;
- 若未显式定义,JVM会根据类结构自动生成一个UID。一旦类结构变更(如增删字段),自动生成的UID会改变,导致反序列化失败(抛出
-
transient关键字
- 修饰的变量不会被序列化,适用于敏感数据(如密码)或无需持久化的临时数据。
- 反序列化后,transient变量会被设为默认值(如null、0)。
三、自定义序列化逻辑
-
重写writeObject()和readObject()
- 若需控制序列化细节(如加密数据),可在类中定义以下方法:
private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // 默认序列化非transient字段 oos.writeUTF(name.toUpperCase()); // 自定义操作 } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 默认反序列化 this.name = ois.readUTF().toLowerCase(); // 自定义操作 }
- 若需控制序列化细节(如加密数据),可在类中定义以下方法:
-
Externalizable接口
- 继承
Externalizable(需实现writeExternal()和readExternal()方法)可完全自定义序列化过程,但需手动处理所有字段。 - 与
Serializable不同,反序列化时要求类必须有公共无参构造器。
- 继承
四、序列化的安全与性能问题
-
漏洞风险
- 反序列化恶意字节流可能导致任意代码执行(如通过重写
readObject()注入逻辑)。 - 防护措施:使用白名单验证对象类型、避免反序列化不可信数据。
- 反序列化恶意字节流可能导致任意代码执行(如通过重写
-
性能优化
- 序列化产生的字节流较大,可改用JSON、Protocol Buffers等替代方案。
- 对于复杂对象,可通过
transient减少序列化数据量。
五、常见面试问题示例
-
问题1:反序列化时如何保证单例对象的唯一性?
- 答案:重写
readResolve()方法,返回单例实例。protected Object readResolve() { return getInstance(); // 返回现有的单例对象 }
- 答案:重写
-
问题2:父类未实现Serializable,子类序列化时父类字段如何处理?
- 答案:父类字段不会被序列化,反序列化时需通过父类构造器初始化(要求父类有无参构造器)。
通过以上步骤,可全面掌握Java序列化的核心机制、定制化方法和实践注意事项。