Java序列化与反序列化完整笔记
2025/12/1大约 6 分钟
Java序列化与反序列化完整笔记
1. 什么是序列化?
序列化(Serialization):将Java对象转换为字节序列的过程,以便可以将对象保存到文件、数据库或通过网络传输。
简单理解:把内存中的对象变成可以存储或传输的数据格式。
Java对象 → 字节流 (序列化)2. 什么是反序列化?
反序列化(Deserialization):将字节序列还原为Java对象的过程,是序列化的逆过程。
字节流 → Java对象 (反序列化)3. 序列化的目的
主要应用场景:
- 持久化存储:将对象保存到文件或数据库中
- 网络传输:在网络上传输对象(如RMI远程调用、分布式系统)
- 进程间通信:不同进程之间传递对象
- 深拷贝:通过序列化和反序列化实现对象的深度复制
- 缓存机制:如Redis存储Java对象
4. 如何实现序列化和反序列化?
步骤1: 实现Serializable接口
类必须实现java.io.Serializable接口(标记接口,无需实现任何方法)
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号
private String name;
private int age;
private String email;
// 构造方法、getter、setter省略
}步骤2: 为类添加serialVersionUID
private static final long serialVersionUID = 1L;作用:
- 用于版本控制,确保序列化和反序列化时类的版本一致
- 如果不显式声明,JVM会自动生成(但不推荐)
- 类结构改变后,如果serialVersionUID不同会抛出
InvalidClassException
推荐做法:
- 显式声明serialVersionUID
- 兼容性升级时保持不变
- 不兼容升级时修改版本号
步骤3: 序列化
使用ObjectOutputStream将对象写入流
import java.io.*;
public class SerializationDemo {
public static void serialize() {
User user = new User("张三", 25, "zhangsan@example.com");
try (FileOutputStream fos = new FileOutputStream("user.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(user); // 序列化
System.out.println("对象已序列化到 user.ser");
} catch (IOException e) {
e.printStackTrace();
}
}
}步骤4: 反序列化
使用ObjectInputStream从流中读取对象
public class DeserializationDemo {
public static void deserialize() {
try (FileInputStream fis = new FileInputStream("user.ser");
ObjectInputStream ois = new ObjectInputStream(fis)) {
User user = (User) ois.readObject(); // 反序列化
System.out.println("姓名: " + user.getName());
System.out.println("年龄: " + user.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}5. 细节说明
5.1 Java序列化不包含静态变量
原因:静态变量属于类,不属于对象,因此不会被序列化。
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private static int count = 0; // 静态变量不会被序列化
public Student(String name) {
this.name = name;
count++;
}
}验证代码:
// 序列化前
Student s1 = new Student("张三");
Student.count = 100; // 修改静态变量
// 序列化后再反序列化
Student s2 = (Student) ois.readObject();
System.out.println(Student.count); // 输出100,不是序列化时的值5.2 transient 关键字
作用:标记不需要序列化的字段
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 不会被序列化
private transient int age; // 不会被序列化
// 反序列化后,transient字段会被赋予默认值
// String -> null
// int -> 0
// boolean -> false
}使用场景:
- 敏感信息(密码、身份证号)
- 可以通过其他字段计算得出的字段
- 临时状态字段
5.3 序列化性能考量
性能对比:
| 序列化方式 | 性能 | 跨语言 | 可读性 |
|---|---|---|---|
| Java原生序列化 | 较慢 | ❌ | 二进制,不可读 |
| JSON (Jackson/Gson) | 中等 | ✅ | 可读 |
| Protobuf | 快 | ✅ | 二进制,不可读 |
| Hessian | 快 | ✅ | 二进制,不可读 |
优化建议:
- 避免序列化大对象:考虑分拆或使用transient
- 使用缓存:相同对象只序列化一次
- 考虑替代方案:
// 使用JSON序列化(跨平台、可读性好) ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(user); User user2 = mapper.readValue(json, User.class);
6. 完整示例代码
import java.io.*;
// 1. 实体类
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // 不会被序列化
private static String company = "ABC公司"; // 不会被序列化
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age +
", password='" + password + "', company='" + company + "'}";
}
}
// 2. 测试类
public class SerializationTest {
public static void main(String[] args) {
// 创建对象
User user = new User("张三", 25, "secret123");
System.out.println("序列化前: " + user);
// 输出: User{name='张三', age=25, password='secret123', company='ABC公司'}
// 序列化
serialize(user, "user.ser");
// 修改静态变量
User.company = "XYZ公司";
// 反序列化
User deserializedUser = deserialize("user.ser");
System.out.println("反序列化后: " + deserializedUser);
// 输出: User{name='张三', age=25, password='null', company='XYZ公司'}
// 注意: password为null(transient), company为新值(静态变量)
}
// 序列化方法
public static void serialize(Object obj, String fileName) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(fileName))) {
oos.writeObject(obj);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 反序列化方法
public static User deserialize(String fileName) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(fileName))) {
return (User) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}7. 常见问题与注意事项
Q1: 如果父类没有实现Serializable?
// 父类未实现Serializable
class Person {
String name;
}
// 子类实现Serializable
class Student extends Person implements Serializable {
int score;
}
// 结果: 只有子类的字段会被序列化,父类字段不会解决方案:父类也实现Serializable接口
Q2: 序列化时引用的对象也必须可序列化
class Address implements Serializable {
String city;
}
class User implements Serializable {
String name;
Address address; // Address也必须实现Serializable
}Q3: serialVersionUID不一致会怎样?
// 序列化时的类
class User implements Serializable {
private static final long serialVersionUID = 1L;
String name;
}
// 反序列化时的类(增加了字段)
class User implements Serializable {
private static final long serialVersionUID = 2L; // 版本号改变
String name;
int age; // 新增字段
}
// 结果: 抛出 InvalidClassException8. 安全注意事项
⚠️ 序列化安全风险:
- 反序列化不受信任的数据可能导致远程代码执行
- 不要反序列化来自不可信来源的数据
防护措施:
// 1. 使用ObjectInputFilter(Java 9+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.User;!*"
);
ois.setObjectInputFilter(filter);
// 2. 考虑使用JSON等文本格式
// 3. 对敏感数据加密后再序列化9. 总结
| 要点 | 说明 |
|---|---|
| 实现接口 | 必须实现Serializable |
| 版本控制 | 显式声明serialVersionUID |
| 静态变量 | 不会被序列化 |
| transient | 标记不序列化的字段 |
| 性能 | 原生序列化较慢,考虑JSON/Protobuf |
| 安全 | 不要反序列化不可信数据 |
推荐实践:
- ✅ 显式声明serialVersionUID
- ✅ 敏感字段使用transient
- ✅ 考虑使用JSON进行跨平台传输
- ✅ 做好版本兼容性管理
- ❌ 避免序列化大对象
- ❌ 不要反序列化不可信数据
