Java 序列化与反序列化

JavaJavaBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

序列化(Serialization)和反序列化(Deserialization)是 Java 中的重要过程,它们允许我们将对象转换为字节流,并分别将其恢复为对象。我们可以使用序列化和反序列化来保存或持久化对象的状态,或者通过网络传输对象。在本实验中,我们将学习如何使用 ObjectInputStreamObjectOutputStream 类在 Java 中序列化和反序列化对象。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("`Java`")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["`Object-Oriented and Advanced Concepts`"]) java(("`Java`")) -.-> java/FileandIOManagementGroup(["`File and I/O Management`"]) java(("`Java`")) -.-> java/SystemandDataProcessingGroup(["`System and Data Processing`"]) java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("`Classes/Objects`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/class_attributes("`Class Attributes`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/constructors("`Constructors`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/modifiers("`Modifiers`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/serialization("`Serialization`") java/FileandIOManagementGroup -.-> java/io("`IO`") java/SystemandDataProcessingGroup -.-> java/object_methods("`Object Methods`") subgraph Lab Skills java/classes_objects -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/class_attributes -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/constructors -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/modifiers -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/serialization -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/io -.-> lab-117950{{"`Java 序列化与反序列化`"}} java/object_methods -.-> lab-117950{{"`Java 序列化与反序列化`"}} end

实现 Serializable 接口

为了使我们的对象可序列化,我们需要实现 Serializable 接口。这是一个标记接口(marker interface),不需要实现任何方法。Serializable 接口用于向 JVM 表明该类的对象可以被序列化。

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;

    // 构造函数和 getter/setter 方法
}

创建要序列化的对象

创建一些 Student 类的对象,我们将在接下来的步骤中对这些对象进行序列化。

Student student1 = new Student("Alice", 22);
Student student2 = new Student("Bob", 21);

序列化对象

要序列化一个对象,我们需要创建一个 ObjectOutputStream 的实例,并调用其 writeObject() 方法,传入我们想要序列化的对象。这会将对象以字节流的形式写入文件,稍后我们可以使用该文件进行反序列化。

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

public class SerializationDemo {
    public static void main(String[] args) {
        try {
            FileOutputStream fileOut = new FileOutputStream("students.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);

            out.writeObject(student1);
            out.writeObject(student2);

            out.close();
            fileOut.close();

            System.out.println("Serialized data is saved in students.ser file");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

反序列化对象

要反序列化一个对象,我们需要创建一个 ObjectInputStream 的实例,并调用其 readObject() 方法,从我们之前序列化的文件中读取字节流。这将返回一个 Object,我们可以将其强制转换为我们原始类的类型。

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

public class SerializationDemo {
    public static void main(String[] args) {
        try {
            FileInputStream fileIn = new FileInputStream("students.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);

            Student student1 = (Student) in.readObject();
            Student student2 = (Student) in.readObject();

            in.close();
            fileIn.close();

            // 打印学生信息
            System.out.println(student1);
            System.out.println(student2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

处理 Transient 字段

如果我们的类中有任何不希望被序列化的字段,可以将它们声明为 transienttransient 字段在序列化和反序列化过程中会被忽略。例如,我们可以在 Student 类中添加一个不希望被序列化的 transient 字段:

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;
    private transient String password;

    // 构造函数、getter/setter 方法
}

自定义序列化和反序列化

我们还可以通过实现 writeObject()readObject() 方法来自定义序列化和反序列化过程。如果我们希望在序列化和反序列化过程中处理 transient 字段,或者执行额外的处理,这会非常有用。

private void writeObject(ObjectOutputStream oos) throws IOException {
    // 默认序列化
    oos.defaultWriteObject();

    // 在写入文件之前加密密码
    String encryptedPassword = encrypt(password);
    oos.writeObject(encryptedPassword);
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    // 默认反序列化
    ois.defaultReadObject();

    // 从文件读取后解密密码
    String encryptedPassword = (String) ois.readObject();
    password = decrypt(encryptedPassword);
}

处理版本控制

如果我们通过添加或删除属性来更新类,那么类的版本会发生变化,这可能会导致在反序列化旧对象时出现错误。我们可以通过使用 serialVersionUID 字段为类指定唯一标识符来处理版本控制。这将确保即使类定义发生变化,序列化和反序列化仍能正常工作。

import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;

    // 构造函数、getter/setter 方法
}

运行代码

编译 SerializationDemo 类,并在终端中使用以下命令运行它:

$ javac SerializationDemo.java
$ java SerializationDemo

输出应显示序列化的数据以及反序列化的对象。

修改 Student 类

通过添加或删除字段来更新 Student 类:

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private String email; // 添加了一个字段

    // 构造函数、getter/setter 方法
}

使用 SerialVersionUID 处理版本控制

现在我们已经修改了类,需要通过更新 serialVersionUID 来处理版本控制。这将确保我们仍然可以反序列化在类修改之前序列化的对象。

public class Student implements Serializable {
    private static final long serialVersionUID = 2L;

    private String name;
    private int age;
    private String email; // 添加了一个字段

    // 构造函数、getter/setter 方法
}

总结

在本实验中,我们学习了如何使用 ObjectInputStreamObjectOutputStream 在 Java 中序列化和反序列化对象。我们还学习了如何处理 transient 字段、自定义序列化和反序列化过程,以及使用 serialVersionUID 处理版本控制。序列化和反序列化是 Java 中保存和传输数据的重要过程,我们可以利用它们存储对象的当前状态,并在需要时恢复它们。

您可能感兴趣的其他 Java 教程