文章目录

Java 序列化:Serializable 接口与 ObjectOutputStream

发布于 2026-04-13 05:17:13 · 浏览 39 次 · 评论 0 条

Java 序列化:Serializable 接口与 ObjectOutputStream

Java 序列化机制允许将对象转换为字节序列,以便存储到硬盘或在网络中传输。通过 Serializable 接口和 ObjectOutputStream,开发者可以轻松实现对象的持久化。以下是其具体使用方法与核心注意事项。


一、 理解序列化与反序列化

区分两个核心概念:

  • 序列化:将 Java 对象转换为字节序列的过程。
  • 反序列化:将字节序列恢复为 Java 对象的过程。

掌握两个主要用途:

  1. 将对象状态持久化保存到文件或数据库中。
  2. 在网络通信中传输对象字节序列。

二、 实现序列化的具体步骤

遵循以下步骤即可完成对象序列化与反序列化操作。

1. 定义可序列化类

实现 java.io.Serializable 接口。该接口是一个标记接口,不包含任何方法。

添加 serialVersionUID 字段。这是一个版本号,用于验证序列化对象的发送者和接收者是否加载了兼容的类。如果不显式定义,JVM 会根据类详情自动生成,一旦类结构发生变化(如新增字段),自动生成的 ID 会改变,导致反序列化失败。

import java.io.Serializable;

public class User implements Serializable {
    // 显式定义版本号,保证类结构修改后仍能兼容旧数据
    private static final long serialVersionUID = 1L;

    private String name;
    private transient String password; // transient 关键字修饰的字段不参与序列化

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', password='" + password + "'}";
    }
}

2. 执行序列化(写入对象)

创建文件输出流(FileOutputStream)指向目标文件。

包装文件输出流为对象输出流(ObjectOutputStream)。

调用 writeObject(Object obj) 方法将对象写入流。

关闭流以释放资源。

import java.io.*;

public class SerializationDemo {
    public static void serialize(String fileName) {
        ObjectOutputStream out = null;
        try {
            // 创建对象输出流
            out = new ObjectOutputStream(new FileOutputStream(fileName));

            // 创建对象并序列化
            User user = new User("张三", "123456");
            out.writeObject(user);

            System.out.println("序列化成功,对象已写入 " + fileName);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3. 执行反序列化(读取对象)

创建文件输入流(FileInputStream)指向源文件。

包装文件输入流为对象输入流(ObjectInputStream)。

调用 readObject() 方法读取对象。

强制转换返回的对象为原始类型。

注意:读取对象的顺序必须与写入时的顺序一致。

public static void deserialize(String fileName) {
    ObjectInputStream in = null;
    try {
        // 创建对象输入流
        in = new ObjectInputStream(new FileInputStream(fileName));

        // 读取对象并反序列化
        User user = (User) in.readObject();

        System.out.println("反序列化成功:" + user);
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

三、 关键控制点与常见陷阱

处理字段序列化时,需注意以下规则和特性。

特性 关键字/概念 行为描述
屏蔽字段 transient transient 修饰的字段不会被序列化。反序列化后,该字段值为默认值(如 null0)。
静态字段 static 静态变量属于类,不属于对象状态,因此不会被序列化。
版本控制 serialVersionUID 建议显式声明。若未声明,JVM 自动生成的 ID 对类结构极其敏感,修改类后可能导致 InvalidClassException

四、 完整运行流程

下图描述了从 Java 对象到二进制数据,再恢复为 Java 对象的完整数据流向:

graph LR A[Java Object] -->|writeObject| B[ObjectOutputStream] B -->|转换| C[Binary Stream] C -->|存储/传输| D[File / Network] D -->|读取| E[ObjectInputStream] E -->|readObject| F[Restored Java Object]

五、 综合代码示例

运行以下代码,验证序列化与反序列化的完整过程,重点观察 transient 字段的变化。

import java.io.*;

public class CompleteDemo {
    public static void main(String[] args) {
        String filePath = "user.ser";

        // 1. 序列化操作
        serializeUser(filePath);

        // 2. 反序列化操作
        deserializeUser(filePath);
    }

    public static void serializeUser(String filePath) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
            User user = new User("李四", "secretPass");
            oos.writeObject(user);
            // oos.close(); // Try-with-resources 会自动关闭
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void deserializeUser(String filePath) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
            User user = (User) ois.readObject();
            // 输出结果中 password 将为 null,因为它被 transient 修饰
            System.out.println("读取到的对象: " + user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String password; // 敏感信息不序列化

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', password='" + password + "'}";
    }
}

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文