《Java 100 天进阶之路》第34篇:Java序列化与反序列化详解
第34篇:Java序列化与反序列化详解
📌 系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第33篇:Java中的static关键字详解 |
➡️ 下一篇:第35篇:Java异常处理最佳实践
一、核心知识点
- 序列化:将对象转换为字节流,便于存储或网络传输
- 反序列化:将字节流恢复为 Java 对象
Serializable接口(标记接口,无方法)transient关键字:跳过序列化serialVersionUID的作用:版本兼容性- 序列化的注意事项(安全、性能、版本)
二、通俗讲解(1分钟开心学)
1. 什么是序列化?
把内存中的 Java 对象变成一串字节(字节流),可以保存到文件、数据库,或者通过网络发送。反序列化就是反过来,把字节流恢复成对象。
2. 如何让一个类可序列化?
实现 java.io.Serializable 接口,它没有任何方法,只是一个“标记”,告诉 JVM:这个类的对象可以被序列化。
3. transient 关键字
如果某个字段不想被序列化(比如密码、缓存数据),可以加上 transient。序列化时会忽略它,反序列化后该字段为默认值(null、0、false)。
4. serialVersionUID
这是一个版本号,用于验证序列化的对象和接收方的类是否兼容。如果没有显式定义,JVM 会根据类结构(字段、方法等)自动生成一个。如果类结构改变(比如增加字段),自动生成的 UID 就会变,导致反序列化旧数据时抛出 InvalidClassException。因此建议手动定义:
private static final long serialVersionUID = 1L;
5. 安全性警告
反序列化不可信的数据可能存在安全风险(反序列化漏洞),攻击者可以构造恶意字节流执行任意代码。所以不要反序列化从网络接收的未经验证的数据。
生活类比:
序列化就像把一堆积木(对象)拆成零件(字节流),装进盒子。反序列化就是按照说明书把零件重新拼回原样。transient就像某些零件不放进盒子(如纸条上的密码)。serialVersionUID就像盒子上写的“拼装图纸版本号”,如果版本不对,就拼不起来。
三、实操代码案例 + 场景说明
场景:保存用户对象到文件,并从中恢复。
import java.io.*;
import java.util.ArrayList;
import java.util.List;
// 可序列化的用户类
class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号,建议显式定义
private String username;
private transient String password; // 密码不序列化
private int age;
public User(String username, String password, int age) {
this.username = username;
this.password = password;
this.age = age;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "', age=" + age + "}";
}
}
public class SerializationDemo {
public static void main(String[] args) throws Exception {
// 1. 序列化:对象 -> 文件
User user = new User("张三", "123456", 25);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
oos.close();
System.out.println("序列化完成");
// 2. 反序列化:文件 -> 对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User restored = (User) ois.readObject();
ois.close();
System.out.println("反序列化得到的对象:" + restored);
// 输出:password 为 null(transient 导致)
// 3. 序列化多个对象到同一个文件
List<User> userList = new ArrayList<>();
userList.add(new User("李四", "pwd1", 30));
userList.add(new User("王五", "pwd2", 28));
ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("users.ser"));
oos2.writeObject(userList); // 序列化整个集合
oos2.close();
ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("users.ser"));
List<User> loadedList = (List<User>) ois2.readObject();
ois2.close();
System.out.println("加载的用户列表:" + loadedList);
}
}
四、避坑要点
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
没有实现 Serializable 就序列化 |
NotSerializableException |
让类实现 Serializable |
没有定义 serialVersionUID,然后修改了类结构 |
反序列化旧数据失败,抛 InvalidClassException |
显式定义 serialVersionUID 并谨慎维护 |
| 静态变量被序列化 | 静态变量不属于对象,不会序列化 | 理解:序列化只保存对象状态,不保存类状态 |
| 反序列化不可信数据 | 安全漏洞(反序列化攻击) | 避免反序列化外部数据;使用白名单或替代格式如 JSON |
五、面试高频考点
Q1:为什么需要 serialVersionUID?
保证序列化和反序列化时的版本一致性。如果不显式定义,JVM 会根据类结构生成哈希值,类稍作改动 UID 就会变,导致旧数据无法反序列化。
Q2:transient 和 static 变量会被序列化吗?
static变量属于类,不序列化;transient变量在序列化时被忽略,反序列化后为默认值。
Q3:如何避免反序列化漏洞?
- 不要反序列化不受信任的数据;2. 使用
ObjectInputStream的子类并重写resolveClass实现白名单;3. 改用 JSON 或 Protobuf 等安全格式。
六、练习题
- 设计:编写一个
Employee类,实现Serializable,其中salary字段不需要序列化,id和name需要。 - 动手:将对象序列化到文件,然后修改类(增加一个字段),再反序列化旧文件,观察异常。然后加上
serialVersionUID再试。 - 分析:
transient和static都导致字段不被序列化,但它们的根本区别是什么?
📊 你的学习进度
- 当前:第34篇 / 共44篇 · 第五阶段:工具类、异常最佳实践、序列化(第32~35篇)
- ✅ 已完成:第1~33篇
- 📖 正在学:第34篇
- ⏳ 待学习:第35~44篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!
👉 下一篇文章预告
《第35篇:Java异常处理最佳实践》
内容简介:异常处理原则(不吞异常)、try-with-resources、异常链与包装、自定义异常规范、日志记录最佳实践。
💡 学完这篇,你将写出更健壮的异常处理代码,面试再问异常处理轻松回答。
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!
📌 除了Java,我也在深挖智能物流实战(出版社WMS、托盘调度、机器学习落地)。如果你对技术在不同领域的实战感兴趣,欢迎点击我的头像,看看专栏《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
更多推荐




所有评论(0)