CodeGym /课程 /JAVA 25 SELF /ObjectOutputStream、ObjectInputStream:操作流

ObjectOutputStream、ObjectInputStream:操作流

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

1. 介绍

在 Java 中,序列化只对显式允许的对象生效。为此,类必须实现一个特殊的接口 — java.io.Serializable

import java.io.Serializable;

public class Person implements Serializable {
    // 字段、构造器、方法
}

Serializable 是一个标记接口:它没有任何方法,只是告诉 JVM——“这个类可以被序列化,请放心!”。 如果尝试序列化一个未实现 Serializable 的类的对象,会得到 NotSerializableException 异常。即使仅有一个字段(或嵌套对象)不可序列化,序列化也不会成功。

ObjectOutputStream 和 ObjectInputStream

  • ObjectOutputStream —— 用于把对象写入流(例如写入文件或通过网络)的类。
  • ObjectInputStream —— 用于从流中读取对象的类。

它们成对工作:一个负责序列化对象,另一个负责反序列化。

主要方法

  • writeObject(Object obj) —— 序列化对象并将其写入流。
  • readObject() —— 从流中读取对象,反序列化并返回。

重要:这两个类构建在常规输入/输出流(OutputStreamInputStream)之上。最常与文件流 FileOutputStreamFileInputStream 一起使用,也可以与网络流配合。

2. 序列化示例

写一个简单示例:序列化并反序列化类 Person 的对象。

步骤 1. 定义类

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    // transient 字段不会被序列化
    private transient String secret;

    public Person(String name, int age, String secret) {
        this.name = name;
        this.age = age;
        this.secret = secret;
    }

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

步骤 2. 将对象序列化到文件

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

public class SerializeDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person("Alice", 30, "likes pizza");

        // 创建用于写入文件的流
        FileOutputStream fileOut = new FileOutputStream("person.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // 保存对象
        out.writeObject(person);

        // 关闭流
        out.close();
        fileOut.close();

        System.out.println("对象已序列化到文件 person.bin");
    }
}

步骤 3. 从文件反序列化对象

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

public class DeserializeDemo {
    public static void main(String[] args) throws Exception {
        // 打开用于从文件读取的流
        FileInputStream fileIn = new FileInputStream("person.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // 恢复对象
        Person person = (Person) in.readObject();

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

        System.out.println("对象已反序列化: " + person);
    }
}

预期输出

对象已序列化到文件 person.bin
对象已反序列化:Person{name='Alice', age=30, secret='null'}

注意!transient 字段不会被序列化。反序列化后它将为 null。这对临时或敏感数据很重要。

3. 限制与要点

所有字段都必须可序列化

如果类中存在未实现 Serializable 的字段(或包含此类对象),序列化将以错误结束。例如,ThreadSocket 类型的字段无法“直接”变成可序列化。

静态字段与 transient 字段

  • 静态字段static)不会被序列化:它们属于类,而不是具体对象。
  • transient 字段 —— 被标记为 transient 的字段会被显式排除在序列化之外,反序列化后会得到默认值(null0 等)。

异常

  • 尝试序列化一个未实现 Serializable 的对象会抛出 NotSerializableException
  • 在反序列化时可能出现错误:文件未找到、类不匹配、数据损坏等。

类的版本

如果在序列化之后修改了类的结构(例如添加/删除字段),反序列化时可能会出现 InvalidClassException。为进行版本控制,可使用专用字段 serialVersionUID(关于它的详细说明会在接下来的讲解中提供)。

4. 实战:把对象序列化/反序列化到文件

假设我们有类 Person,并希望把人员列表保存到文件并再读回来。

Person 类(可序列化)

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private transient String secret; // 不会被序列化

    public Person(String name, int age, String secret) {
        this.name = name;
        this.age = age;
        this.secret = secret;
    }

    @Override
    public String toString() {
        return name + " (" + age + "), secret: " + secret;
    }
}

序列化人员列表

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class SerializeListDemo {
    public static void main(String[] args) throws Exception {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30, "likes pizza"));
        people.add(new Person("Bob", 25, "hates broccoli"));

        FileOutputStream fileOut = new FileOutputStream("people.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // 保存列表
        out.writeObject(people);

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

        System.out.println("人员列表已序列化。");
    }
}

反序列化人员列表

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;

public class DeserializeListDemo {
    public static void main(String[] args) throws Exception {
        FileInputStream fileIn = new FileInputStream("people.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // 恢复列表
        List<Person> people = (List<Person>) in.readObject();

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

        for (Person p : people) {
            System.out.println(p);
        }
    }
}

结果:

Alice (30), secret: null
Bob (25), secret: null

5. 常见错误

错误 1:类未实现 Serializable。 如果忘记添加 implements Serializable,在尝试序列化时会得到 NotSerializableException。这是最常见且最容易修复的错误。

错误 2:不可序列化的字段。 如果对象包含不可序列化的字段(例如 ThreadSocket 或任何没有实现 Serializable 的类型),序列化会“失败”。请将此类字段标记为 transient,或让其可序列化。

错误 3:类结构发生变化。 如果对象已被序列化,而稍后类发生变化(添加/删除字段),读取时可能出现 InvalidClassException。请指定 serialVersionUID 以稳定控制版本。

错误 4:试图序列化静态字段。 具有 static 修饰符的字段不会被序列化。反序列化后,它们的值将是类中的当前值,而不是序列化时的值。

错误 5:未关闭流。 如果在使用后不关闭流,可能导致文件损坏或资源泄漏。使用 try-with-resources 或显式关闭流。

错误 6:类不匹配。 如果类被重命名或移动到其他包中,反序列化将失败——需要与存入流时的类名与包名完全一致。

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