Skip to content

Java 序列化 (Serialization)

Java 序列化是一种将对象的状态信息转换为可以存储或传输的格式(如字节序列)的机制。反序列化则是从字节序列中重新构建出对象。这个功能对于持久化对象状态或在网络上传输对象非常有用。

什么是序列化?

想象一下,您在程序中创建了一个复杂的对象,它包含了各种状态数据。当程序关闭时,这个对象以及它的状态就会从内存中消失。如果您希望在下次程序启动时能恢复这个对象,就需要将它序列化(写入到文件或数据库),并在需要时反序列化(从文件或数据库中读回)。

Serializable 接口

要使一个类的对象可以被序列化,该类必须实现 java.io.Serializable 接口。

  • Serializable 是一个标记接口 (marker interface),它本身没有任何方法。它只是向 JVM 表明该类的对象是允许被序列化的。
java
import java.io.Serializable;

public class User implements Serializable {
    // 类的属性
    private String name;
    private int age;

    // 构造方法、getter、setter 等...
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

序列化和反序列化对象

  • ObjectOutputStream: 用于将对象写入到一个输出流(如 FileOutputStream)。它的 writeObject() 方法执行序列化操作。
  • ObjectInputStream: 用于从一个输入流中读取对象。它的 readObject() 方法执行反序列化操作。

示例代码

java
import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        // 1. 创建一个要序列化的对象
        User user = new User("Alice", 30);
        String filename = "user.ser";

        // 2. 序列化对象到文件
        try (FileOutputStream fileOut = new FileOutputStream(filename);
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            
            out.writeObject(user);
            System.out.println("对象已序列化到 " + filename);

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 3. 从文件反序列化对象
        User deserializedUser = null;
        try (FileInputStream fileIn = new FileInputStream(filename);
             ObjectInputStream in = new ObjectInputStream(fileIn)) {

            deserializedUser = (User) in.readObject();
            System.out.println("\n对象已从 " + filename + " 反序列化");
            System.out.println("反序列化后的对象: " + deserializedUser);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

transient 关键字

默认情况下,对象的所有非静态字段都会被序列化。如果您不希望某个字段被序列化(例如,该字段是临时的,或者包含敏感信息如密码),可以使用 transient 关键字来标记它。

java
public class User implements Serializable {
    private String username;
    private transient String password; // 此字段不会被序列化
    // ...
}

当一个包含 transient 字段的对象被反序列化时,该字段的值将是其类型的默认值(对于引用类型是 null,对于数值类型是 0,对于布尔类型是 false)。

serialVersionUID

序列化机制通过一个名为 serialVersionUID 的版本号来验证序列化的对象和加载它的类是否兼容。这是一个 private static final long 类型的字段。

  • 作用:在反序列化时,JVM 会比较文件中的 serialVersionUID 和类中的 serialVersionUID。如果两者不匹配,将抛出 InvalidClassException
  • 为什么重要:如果您没有显式声明 serialVersionUID,Java 编译器会根据类的结构(字段、方法等)自动生成一个。这意味着如果您修改了类(例如添加或删除了一个字段),自动生成的 serialVersionUID 就会改变,导致无法反序列化旧版本的对象。

最佳实践是始终在实现了 Serializable 接口的类中显式声明 serialVersionUID

java
public class User implements Serializable {
    // 显式声明 serialVersionUID
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    // ...
}

通过显式声明,即使您对类做了一些不影响序列化兼容性的修改(如添加一个方法),serialVersionUID 保持不变,仍然可以成功反序列化旧对象。

本站内容仅供学习和研究使用。