一、一句话理解序列化

  • 序列化:把内存中的 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
在这里插入图片描述
由此,不难发现:

  1. serialVersionUID是序列化前后的唯一标识符
  2. 默认如果没有人为显式定义过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、控制好哪些字段需要序列化。这样你就能轻松应对对象的存储和传输啦!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐