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() —— 从流中读取对象,反序列化并返回。
重要:这两个类构建在常规输入/输出流(OutputStream 和 InputStream)之上。最常与文件流 FileOutputStream 和 FileInputStream 一起使用,也可以与网络流配合。
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 的字段(或包含此类对象),序列化将以错误结束。例如,Thread 或 Socket 类型的字段无法“直接”变成可序列化。
静态字段与 transient 字段
- 静态字段(static)不会被序列化:它们属于类,而不是具体对象。
- transient 字段 —— 被标记为 transient 的字段会被显式排除在序列化之外,反序列化后会得到默认值(null、0 等)。
异常
- 尝试序列化一个未实现 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:不可序列化的字段。 如果对象包含不可序列化的字段(例如 Thread、Socket 或任何没有实现 Serializable 的类型),序列化会“失败”。请将此类字段标记为 transient,或让其可序列化。
错误 3:类结构发生变化。 如果对象已被序列化,而稍后类发生变化(添加/删除字段),读取时可能出现 InvalidClassException。请指定 serialVersionUID 以稳定控制版本。
错误 4:试图序列化静态字段。 具有 static 修饰符的字段不会被序列化。反序列化后,它们的值将是类中的当前值,而不是序列化时的值。
错误 5:未关闭流。 如果在使用后不关闭流,可能导致文件损坏或资源泄漏。使用 try-with-resources 或显式关闭流。
错误 6:类不匹配。 如果类被重命名或移动到其他包中,反序列化将失败——需要与存入流时的类名与包名完全一致。
GO TO FULL VERSION