Java 序列化与反序列化终极解析
Java 序列化与反序列化终极解析
1. 核心概念
(1) 什么是序列化?
-  定义:将对象转换为字节流的过程(对象 → 字节) 
-  目的: -  持久化存储(如保存到文件) 
-  网络传输(如RPC调用) 
-  深拷贝实现 
 
-  
(2) 什么是反序列化?
-  定义:将字节流还原为对象的过程(字节 → 对象) 
-  关键点: -  不会调用构造方法 
-  字段值直接从字节流读取 
 
-  
2. 核心API
(1) 序列化相关类
| 类/接口 | 作用 | 
|---|---|
| Serializable | 标记接口(无方法) | 
| Externalizable | 提供自定义序列化(需实现2个方法) | 
| ObjectOutputStream | 序列化输出流 | 
| ObjectInputStream | 反序列化输入流 | 
(2) 关键方法
java
// 序列化
objectOutputStream.writeObject(obj);// 反序列化
Object obj = objectInputStream.readObject();3. 完整流程解析
(1) 序列化流程
-  检查对象是否实现 Serializable
-  递归序列化所有非 transient字段
-  写入类描述信息(含 serialVersionUID)
-  写入字段数据 
(2) 反序列化流程
-  读取并验证 serialVersionUID
-  分配对象内存(不调用构造方法) 
-  递归填充字段值 
-  对于 Externalizable对象,调用readExternal()
4. 代码示例
(1) 基础序列化
java
// 可序列化类
class Person implements Serializable {private static final long serialVersionUID = 1L;private String name;private transient int age; // 不参与序列化// 构造方法、getter/setter省略
}// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {oos.writeObject(new Person("Alice", 25));
}// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {Person p = (Person) ois.readObject();System.out.println(p.getName()); // 输出AliceSystem.out.println(p.getAge()); // 输出0(transient字段)
}(2) 自定义序列化(Externalizable)
java
class Student implements Externalizable {private String name;// 必须有无参构造器public Student() {}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeUTF(name);}@Overridepublic void readExternal(ObjectInput in) throws IOException {this.name = in.readUTF();}
}5. 关键机制
(1) serialVersionUID
-  作用:版本控制,防止类结构变更导致反序列化失败 
-  生成方式: -  显式声明: private static final long serialVersionUID = 1L;
-  隐式生成:根据类结构计算hash值 
 
-  
(2) 序列化规则
| 字段类型 | 是否序列化 | 说明 | 
|---|---|---|
| 普通实例字段 | 是 | |
| transient字段 | 否 | 如密码字段 | 
| static字段 | 否 | 属于类而非对象 | 
| 未实现Serializable | 抛出异常 | 所有引用字段必须可序列化 | 
6. 高级特性
(1) 自定义序列化
java
private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject(); // 默认序列化out.writeUTF(encrypt(password)); // 自定义处理
}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject(); // 默认反序列化this.password = decrypt(in.readUTF()); // 自定义处理
}(2) 序列化代理模式
java
private Object writeReplace() {return new SerializationProxy(this);
}private static class SerializationProxy implements Serializable {private final String data;SerializationProxy(OriginalClass obj) {this.data = obj.getData();}private Object readResolve() {return new OriginalClass(data);}
}7. 常见问题与解决方案
(1) 反序列化漏洞
-  风险:攻击者可能构造恶意字节流执行任意代码 
-  防护: -  使用 ObjectInputFilter设置白名单
 java 
-  
ObjectInputFilter filter = info -> info.serialClass() == Person.class ? Status.ALLOWED : Status.REJECTED;
ois.setObjectInputFilter(filter);(2) 性能优化
-  替代方案: -  JSON(Jackson/Gson) 
-  二进制协议(Protocol Buffers) 
-  Kryo(高性能Java序列化) 
 
-  
8. 记忆技巧
(1) 核心口诀
"序列化要接口,transient能跳过
static不算数,UID保平安
构造方法不执行,字段直接写内存"
(2) 流程图解
复制
序列化: [对象] → [检查Serializable] → [写入元数据] → [递归写字段] → [字节流]反序列化: [字节流] → [验证UID] → [分配内存] → [填充字段] → [返回对象]
9. 面试高频问题
-  Q: 反序列化时如何避免调用构造方法? 
 A: JVM直接分配内存并从字节流填充数据
-  Q: 为什么 Serializable是空接口?
 A: 标记接口,仅用于类型检查
-  Q: 如何实现深拷贝? 
 A: 序列化后再反序列化
-  Q: Externalizable和Serializable区别?
 A: 前者需实现方法,后者自动序列化
10. 最佳实践
-  安全性: -  敏感字段标记 transient
-  使用 ObjectInputFilter
 
-  
-  版本控制: -  始终显式声明 serialVersionUID
 
-  
-  性能: -  避免序列化大对象 
-  考虑替代方案(如ProtoBuf) 
 
-  
-  代码健壮性: -  实现 readObject()时保持防御性编程
-  对不可变对象使用序列化代理模式 
 
-  
通过这个完整体系,你已掌握Java序列化的:
 ✅ 核心机制 ✅ 安全风险 ✅ 性能优化 ✅ 工程实践
