教程中心 / Java教程 / 序列化

序列化

15分钟 I/O流

引言:什么是序列化?

序列化(Serialization)是将对象转换为字节序列的过程,反序列化(Deserialization)是将字节序列恢复为对象的过程。主要用于对象持久化网络传输

序列化类比:快递打包

发送方(序列化):
你有一个Java对象
    ↓ 序列化
变成一盒积木(字节序列)
    ↓ 运输
寄送快递

接收方(反序列化):
收到一盒积木
    ↓ 反序列化
组装成原来的Java对象

序列化就是"打包寄送",反序列化就是"拆包组装"

序列化应用场景

对象持久化:将对象保存到文件、数据库
网络传输:RMI远程调用、WebService
缓存:Redis、Memcached存储对象
Session存储:分布式Session

Serializable接口

要让一个类的对象能够序列化,必须实现java.io.Serializable接口。

基本用法

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String password;  // 不参与序列化
}

重要特性

  • Serializable是一个标记接口(没有方法)
  • 实现该接口表示"这个类的对象可以被序列化"
  • 如果类没有实现Serializable,其对象将不能被序列化

序列化与反序列化

序列化对象(写入)

import java.io.*;

public class SerializationDemo {
    public static void serialize(String filename, Object obj) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(filename))) {
            oos.writeObject(obj);
            System.out.println("序列化成功!");
        }
    }

    public static void main(String[] args) {
        Person person = new Person("张三", 25, "password123");
        try {
            serialize("person.ser", person);
        } catch (IOException e) {
            System.out.println("序列化失败: " + e.getMessage());
        }
    }
}

反序列化对象(读取)

public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
    try (ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream(filename))) {
        return ois.readObject();
    }
}

public static void main(String[] args) {
    try {
        Person p = (Person) deserialize("person.ser");
        System.out.println("反序列化成功: " + p);
    } catch (IOException | ClassNotFoundException e) {
        System.out.println("反序列化失败: " + e.getMessage());
    }
}

serialVersionUID

serialVersionUID是序列化版本号,用于验证序列化和反序列化的类是否兼容。

情况说明
显式声明private static final long serialVersionUID = 1L;
未声明编译器根据类结构自动生成(可能不一致)
版本不匹配抛出InvalidClassException

为什么需要声明serialVersionUID?

// 类修改前
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;  // 版本1
    private String name;
    private int age;
}

// 类修改后(新增字段)
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;  // 仍然是版本1
    private String name;
    private int age;
    private String address;  // 新增字段
}

// 序列化时的Person对象可以反序列化
// 新增字段address会被设为默认值null

transient关键字

transient修饰的字段在序列化时会被忽略,不保存到字节流中。

使用场景

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String password;          // 需要序列化
    private transient String token;  // 不需要序列化
    private transient Date loginTime; // 不需要序列化
}

注意:transient字段在反序列化后会被设置为默认值(null、0、false)。

应该使用transient的场景

敏感信息:密码、密钥等安全数据
计算得出的值:缓存、派生数据
非持久化状态:临时变量、线程相关

序列化深入理解

序列化的局限性

// 不能序列化的内容:
// 1. 线程、Socket连接等运行时对象
// 2. 图形、GUI组件
// 3. 内部类(隐式包含外部类引用)
// 4. 循环引用的对象(会StackOverflow)

静态字段序列化

public class Test implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private static int count;  // 静态字段不序列化
    private transient int temp; // transient字段不序列化
}

// 序列化时:
// - name会被保存
// - count(静态)不会被保存
// - temp会被忽略

// 反序列化时:
// - name恢复原值
// - count保持当前JVM中的值(不恢复)
// - temp恢复为默认值0

自定义序列化

public class CustomSerializable implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Date createdDate;

    // 自定义序列化方法(可选)
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();  // 先默认序列化
        out.writeLong(createdDate.getTime());  // 自定义处理
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();  // 先默认反序列化
        createdDate = new Date(in.readLong());  // 自定义处理
    }
}

实战:用户Session序列化

import java.io.*;
import java.util.*;

public class SessionSerialization {

    public static void main(String[] args) {
        UserSession session = new UserSession(
            "user123",
            "张三",
            "token_abc123"
        );

        System.out.println("原始Session: " + session);

        // 序列化到文件
        String filename = "session.ser";
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(filename))) {
            oos.writeObject(session);
            System.out.println("序列化成功!");
        } catch (IOException e) {
            System.out.println("序列化失败: " + e.getMessage());
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(filename))) {
            UserSession restored = (UserSession) ois.readObject();
            System.out.println("反序列化Session: " + restored);
            System.out.println("Token(被transient忽略): " + restored.getToken());
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("反序列化失败: " + e.getMessage());
        }
    }
}

class UserSession implements Serializable {
    private static final long serialVersionUID = 1L;
    private String userId;
    private String username;
    private transient String token;  // 不序列化
    private transient Date accessTime;  // 不序列化

    public UserSession(String userId, String username, String token) {
        this.userId = userId;
        this.username = username;
        this.token = token;
        this.accessTime = new Date();
    }

    public String getToken() {
        return token;  // 反序列化后为null
    }

    @Override
    public String toString() {
        return "UserSession{" +
            "userId='" + userId + '\'' +
            ", username='" + username + '\'' +
            ", token='" + token + '\'' +
            '}';
    }
}

序列化注意事项

性能问题:序列化速度较慢,注意使用场景
安全性:敏感数据不要序列化,或使用加密
版本兼容性:修改类后保持serialVersionUID一致
单例模式:序列化可能破坏单例,需要实现readResolve

小结

  • 序列化将对象转换为字节序列,用于持久化和传输
  • 类必须实现Serializable接口才能序列化
  • transient修饰的字段不参与序列化,反序列化后为默认值
  • serialVersionUID用于版本兼容性验证
  • 静态字段不参与序列化
  • 敏感信息不要序列化,或使用自定义序列化
☕ Java 在线代码编辑器
📝 运行结果