CodeGym /课程 /JAVA 25 SELF /Serializable 接口:基本原理

Serializable 接口:基本原理

JAVA 25 SELF
第 42 级 , 课程 1
可用

1. Serializable 接口

还记得上一节的示例吗?我们想要序列化的 Java 类必须实现一个特殊的接口 — java.io.Serializable。这是所谓的标记接口:它不包含任何方法,只是“标记”该类可以被序列化。如果类实现了这个接口,JVM 就会允许用标准手段序列化它的对象。

不建议对所有东西都进行序列化,因为并不是所有对象都能或都需要被保存为字节。有些对象依赖于操作系统状态、已打开的文件或网络连接。因此,Java 要求显式地将类标记为可序列化。

标记接口就像盒子上的“允许打包”贴纸。如果没有这张贴纸——打包工(JVM)就会拒绝工作。

示例:声明一个可序列化的类

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;
    }

    // 为了美观:toString() 方法
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

请注意:

  • 我们只是在类声明中添加了 implements Serializable
  • 不需要实现任何方法(该接口是空的)。
  • 所有可以序列化的 Java 标准类(例如 ArrayListHashMapString)都已经实现了 Serializable

2. 如何让自己的类可序列化

规则 №1:只需添加 implements Serializable

对于类本身,这就足够了。但还有一些细节!

重要:所有被引用的对象也必须是可序列化的。
如果你的类有指向其他对象的引用字段,它们也必须是可序列化的。例如:

public class Profile implements Serializable {
    private User user; // User 必须是可序列化的!
    private int level;
}

只要有一个字段不可序列化,在尝试序列化时就会抛出异常。

3. 序列化与反序列化示例

我们来看看如何把对象序列化到文件并再反序列化回来。为此可以使用类 ObjectOutputStreamObjectInputStream

示例:将 User 对象序列化到文件

import java.io.*;

public class SerializeDemo {
    public static void main(String[] args) {
        User user = new User("Alice", 30);

        // 将对象保存到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("对象已成功序列化到文件 user.ser");
        } catch (IOException e) {
            System.out.println("序列化错误: " + e.getMessage());
        }
    }
}

这里发生了什么?

  • 创建一个 User 对象。
  • 打开一个 ObjectOutputStream,它写入文件 "user.ser"
  • 调用 writeObject(user)。此时 JVM 将对象转换为字节流并保存到文件。

示例:从文件反序列化对象

import java.io.*;

public class DeserializeDemo {
    public static void main(String[] args) {
        // 从文件中读取对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User user = (User) ois.readObject();
            System.out.println("对象已成功恢复: " + user);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("反序列化错误: " + e.getMessage());
        }
    }
}

这里发生了什么?

  • 打开 ObjectInputStream,从文件 "user.ser" 中读取。
  • 调用 readObject()。JVM 从字节中恢复对象。
  • 别忘了把结果强制转换为所需类型(User),因为 readObject() 返回的是 Object
  • 如果在反序列化时找不到 User 类,可能会抛出 ClassNotFoundException

整合:序列化与反序列化

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        User user = new User("Bob", 22);

        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("序列化完成!");
        } catch (IOException e) {
            System.out.println("序列化错误: " + e.getMessage());
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User loaded = (User) ois.readObject();
            System.out.println("反序列化完成! " + loaded);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("反序列化错误: " + e.getMessage());
        }
    }
}

结果:

序列化完成!
反序列化完成! User{name='Bob', age=22}

4. 序列化时“底层”发生了什么

当你调用 writeObject 时,JVM 首先会检查该类是否实现了接口 Serializable。如果类未被标记为可序列化,就会抛出异常。随后 JVM 会遍历对象的所有普通字段(即不是 static、也不是 transient 的字段),并将它们的值写入字节流。如果这些字段中包含其他对象,则会对它们递归执行序列化,但前提是它们也实现了 Serializable

在反序列化时,对象会在不调用常规构造函数的情况下被创建,其字段会用保存的值填充——就像“没有构造函数的构造函数”从字节流中让对象复活。

有些字段不会被序列化。静态字段(static)属于类本身,而不是某个对象,因此它们的值不会被保存。被标记为 transient 的字段也会被跳过——这对于临时数据、缓存或密码等敏感信息很有用。

序列化流程示意图

flowchart TB
    A[内存中的 User 对象] -- writeObject --> B[ObjectOutputStream]
    B -- 保存字节 --> C[文件 user.ser]
    C -- readObject --> D[ObjectInputStream]
    D -- 恢复 --> E[内存中的 User 对象]

5. 使用 Serializable 时的常见错误

错误 №1:指向不可序列化对象的字段。如果在类 User 中出现一个字段,其类型例如是 ThreadSocket,序列化将无法工作。并不是所有对象都可以序列化——务必牢记这一点!

错误 №2:不可序列化的嵌套类。如果类 User 包含一个非 static 的内部类,序列化可能会失败。最好使用 static 嵌套类或独立的类文件。

错误 №3:尝试序列化 static 字段。静态字段不会被序列化——它们属于类而非对象。反序列化后,static 字段的值将是类中定义的值,而不是已序列化对象中保存的值。

错误 №4:类版本不匹配。如果在序列化之后修改了类的结构(例如添加或删除字段),然后尝试反序列化旧对象,可能会抛出 InvalidClassException。为进行版本控制,使用一个名为 serialVersionUID 的特殊字段——我们将在下一节课中详细讨论它。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION