Java 序列化入门:把对象变成 “文件” 的魔法
一、一句话理解序列化
- 序列化:把内存中的 Java 对象,变成一串字节(类似 “压缩包”),方便存文件或发网络。
- 反序列化:把字节串变回原来的 Java 对象(类似 “解压还原”)。
比如玩游戏时存档,就是把当前游戏状态(对象)序列化存到文件;下次读档,就是反序列化把文件变回游戏状态。
二、为什么需要序列化?
3 个常见场景,让你明白它的用处:
✅ 保存数据:把程序中的对象存到文件 / 数据库,下次打开程序还能恢复(比如游戏存档)。
✅网络传输:在两个服务器之间发对象(比如微服务之间传用户信息),得先变成字节流才能通过网络发送。
✅缓存对象:像 Redis 这类缓存工具,存对象时需要先序列化,取的时候再反序列化。
Java 序列化是为了保存各种对象在内存中状态,并且可以把保存的对象状态再读出来。
三、3 步搞定序列化实战
第一步:实现Serializable接口
public class Student implements Serializable {
private String name;
private Integer age;
}
第二步:序列化对象(对象->字节)
java.io.ObjectOutputStream:表示对象输出流
它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
// 创建对象
Student student = new Student("小明", 18, "123456");
// 序列化到文件
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("student.dat"))) {
oos.writeObject(student);
}
第三步:反序列化恢复对象(字节->对象)
java.io.ObjectInputStream:表示对象输入流
它的readObject()方法从源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("student.dat"))) {
Student restored = (Student) ois.readObject();
System.out.println(restored.getName()); // 输出"小明"
}
序列化过程:
代码示例:
package com.heima.test;
import lombok.Data;
import java.io.*;
/**
* ClassName: Student
*
* @Author rapden
* @Create 2025-07-22 17:26
*/
@Data
public class Student implements Serializable{
private String name;
private Integer age;
private Integer score;
@Override
public String toString() {
return "Student:" + '\n' +
"name = " + this.name + '\n' +
"age = " + this.age + '\n' +
"score = " + this.score + '\n'
;
}
public static void serialize() throws IOException {
Student student = new Student();
student.setName("CodeSheep");
student.setAge( 18 );
student.setScore( 1000 );
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(("Student.txt"))));
oos.writeObject(student);
oos.close();
System.out.println("序列化成功!已经生成student.txt文件");
System.out.println("==============================================");
}
public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(new File("Student.txt")));
Student student = (Student) is.readObject();
is.close();
System.out.println("反序列化结果为:"+student);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
serialize();
unserialize();
}
}
运行结果:
Student.txt文件内容如下:
四、关键知识点:序列化的 “潜规则”
1. Serializable接口真相:
点开源码:发现是一个空接口
但当我们在 Student类去掉 implements Serializable 时会有报错:java.io.NotSerializableException
跟踪源码,在 ObjectOutputStream 类的的 writeObject0 方法中发现
如果一个对象不是字符串、数组、枚举类,而且也没实现 Serializable接口的话,在序列化时会抛出 java.io.NotSerializableException 异常。
在此,恍然大悟!!它只是个标记接口,告诉JVM:"这个类可以被序列化"
然而真正的序列化动作并不需要靠它完成
2. serialVersionUID :序列化身份证?
做个实验:我们修改下Student类,比如增加个字段 int testc; 然后调用unserialize();方法,拿Student.txt里的内容反序列化,会发现报错
java.io.InvalidClassException: com.heima.test.Student; local class incompatible: stream classdesc serialVersionUID = -6907151255254397368, local class serialVersionUID = 9162884309291443057
由此,不难发现:
serialVersionUID是序列化前后的唯一标识符- 默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!
第1个问题: serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。
第2个问题: 如果在定义一个可序列化的类时,没有人为显式地给它定义一个serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化!
所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值!
3. 两种特殊情况
static修饰的字段:序列化存的是对象状态,而static是类的状态,所以不存。transient修饰的字段:手动标记 “不序列化”,比如密码等敏感信息:
对于第二点,就需要了解一下transient修饰符的作用了。
如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。
比如在之前定义的Student类中,加入一个密码字段,但是不希望序列化到
运行测试代码可以发现:
五、真实应用场景
1. 游戏存档
// 保存游戏状态
try (ObjectOutputStream oos = ...) {
oos.writeObject(gameState);
}
// 加载存档
try (ObjectInputStream ois = ...) {
GameState saved = (GameState) ois.readObject();
}
2. 微服务通信
// 服务A发送对象
socket.getOutputStream().write(ObjectToBytes(user));
// 服务B接收对象
User user = (User) BytesToObject(socket.getInputStream());
3. Redis缓存对象
// 存储序列化对象
redis.set("user:1001".getBytes(), serialize(user));
// 读取反序列化
User user = deserialize(redis.get("user:1001".getBytes()));
六、 最佳实践
- 始终声明serialVersionUID
- 避免类结构变化导致反序列化失败
- 使用IDE自动生成固定ID
- 敏感字段(比如身份证、密码)用
transient保护
private transient String creditCardNumber;
-
谨慎序列化大型对象
- 可能影响性能
- 考虑其他序列化方案(JSON/ProtoBuf)
-
自定义序列化逻辑:如果需要特殊处理,比如加密,可以实现这两个方法
private void writeObject(ObjectOutputStream out) throws IOException {
// 自定义序列化(比如加密字段)
out.defaultWriteObject(); // 先执行默认序列化
out.writeUTF(encrypt(password)); // 手动处理密码
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 自定义反序列化(解密)
in.defaultReadObject();
password = decrypt(in.readUTF());
}
总结
序列化就像给对象 “拍照片”,反序列化就是让照片里的对象 “活过来”。记住 3 个关键点:实现Serializable接口、加serialVersionUID、控制好哪些字段需要序列化。这样你就能轻松应对对象的存储和传输啦!
更多推荐


所有评论(0)