Java Serializable 接口
上次修改:2025 年 4 月 26 日
Java 中的 Serializable 接口使对象能够被转换为字节流以便存储或传输。它不需要实现任何方法,充当一个标记接口。
序列化对于将对象持久化到文件、数据库或通过网络发送它们至关重要。Serializable 有助于在各种应用程序中保存和恢复对象状态。
Serializable 接口概述
Serializable 接口是 java.io 包的一部分,是一个标记接口,表明一个类有资格进行序列化。 标记接口是一种特殊的接口,不包含任何方法或字段,但用作 Java 运行时环境关于类功能的信号。 当一个类实现 Serializable 接口时,它的对象可以使用 ObjectOutputStream 转换为字节流。 然后,可以将此字节流保存到文件中、通过网络传输或用于其他需要保存对象状态的上下文中。
反序列化是一个相反的过程,先前序列化的字节流被读取并使用 ObjectInputStream 重构为对象。这允许对象在不同的执行或环境中保持其状态和结构,从而实现持久存储或数据传输。
在设计用于序列化的类时,必须考虑以下几个因素,以确保健壮和安全的行为
- 版本控制: 序列化对象包含一个
serialVersionUID,用于在反序列化期间验证兼容性。如果类的serialVersionUID与序列化对象的serialVersionUID不匹配,则会发生InvalidClassException。 建议开发人员显式定义serialVersionUID,以避免类定义更改时出现兼容性问题。 - 瞬态字段: 使用
transient关键字标记的字段将从序列化中排除。 这对于敏感数据(如密码)或可以重新计算的字段(如缓存值)非常有用,以避免不必要的存储或安全风险。 - 自定义序列化逻辑: 类可以通过定义
writeObject和readObject方法来覆盖默认的序列化行为。 这对于确保正确处理不可序列化的字段或在反序列化期间添加额外的验证特别有用。 - 安全性: 反序列化可能成为漏洞的潜在载体,例如代码注入或对象欺骗。 开发人员应仔细验证输入流,并避免从不受信任的来源反序列化数据。
总的来说,Serializable 接口在实现对象持久性和进程间通信方面起着至关重要的作用。 但是,开发人员必须谨慎行事并遵循最佳实践,以确保应用程序中高效、安全和向后兼容的序列化。
基本序列化和反序列化
此示例演示了实现 Serializable 的简单类的基本序列化和反序列化,将对象保存并加载到文件。
package com.zetcode;
import java.io.*;
public class BasicSerialization {
static class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) {
// Serialize
Person person = new Person("Alice", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
oos.writeObject(person);
System.out.println("Serialized: " + person);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person deserialized = (Person) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
此程序定义了一个实现 Serializable 的 Person 类。它将实例序列化到文件,然后将其反序列化回来。
try-with-resources 确保流被正确关闭。 输出确认反序列化的对象与原始对象匹配,证明了基本的序列化。
使用瞬态字段
此示例演示了如何使用 transient 关键字从序列化中排除字段,这对于敏感或不可序列化的数据非常有用。
package com.zetcode;
import java.io.*;
public class TransientFields {
static class User implements Serializable {
private String username;
private transient String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
public static void main(String[] args) {
// Serialize
User user = new User("bob", "secret123");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("Serialized: " + user);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User deserialized = (User) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
User 类将 password 字段标记为 transient,将其从序列化中排除。 反序列化后,该字段为 null。
这对于敏感数据(如密码)或无法序列化的字段非常有用,从而确保对象持久性期间的安全性和兼容性。
Serial Version UID
此示例说明了使用 serialVersionUID 来管理序列化期间的类版本控制,从而防止兼容性问题。
package com.zetcode;
import java.io.*;
public class SerialVersionUID {
static class Employee implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{name='" + name + "', salary=" + salary + "}";
}
}
public static void main(String[] args) {
// Serialize
Employee emp = new Employee("Carol", 50000);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("employee.ser"))) {
oos.writeObject(emp);
System.out.println("Serialized: " + emp);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("employee.ser"))) {
Employee deserialized = (Employee) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Employee 类定义了一个 serialVersionUID 以确保跨类版本的兼容性。 这可以防止反序列化期间的 InvalidClassException。
显式声明 serialVersionUID 是在类结构随时间演变时保持序列化兼容性的最佳实践。
自定义序列化
此示例演示了如何通过实现 writeObject 和 readObject 方法来自定义序列化,以便特殊处理对象状态。
package com.zetcode;
import java.io.*;
public class CustomSerialization {
static class Student implements Serializable {
private String name;
private transient int grade;
public Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(grade + 10); // Custom logic: increment grade
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.grade = ois.readInt();
}
@Override
public String toString() {
return "Student{name='" + name + "', grade=" + grade + "}";
}
}
public static void main(String[] args) {
// Serialize
Student student = new Student("Dave", 85);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("student.ser"))) {
oos.writeObject(student);
System.out.println("Serialized: " + student);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("student.ser"))) {
Student deserialized = (Student) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Student 类通过在序列化和反序列化期间修改 grade 字段来自定义序列化,在其值上加 10。
自定义序列化对于转换数据、处理瞬态字段或确保与对象持久性期间的旧系统兼容非常有用。
序列化复杂对象
此示例演示了序列化对其他可序列化对象具有引用的对象,展示了如何在序列化中处理对象图。
package com.zetcode;
import java.io.*;
public class ComplexSerialization {
static class Department implements Serializable {
private String name;
public Department(String name) {
this.name = name;
}
@Override
public String toString() {
return "Department{name='" + name + "'}";
}
}
static class Worker implements Serializable {
private String name;
private Department department;
public Worker(String name, Department department) {
this.name = name;
this.department = department;
}
@Override
public String toString() {
return "Worker{name='" + name + "', department=" + department + "}";
}
}
public static void main(String[] args) {
// Serialize
Department dept = new Department("Engineering");
Worker worker = new Worker("Eve", dept);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("worker.ser"))) {
oos.writeObject(worker);
System.out.println("Serialized: " + worker);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("worker.ser"))) {
Worker deserialized = (Worker) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Worker 类包含对 Department 对象的引用。 这两个类都实现了 Serializable,允许序列化整个对象图。
这展示了序列化如何处理复杂的关系,自动序列化引用的对象,前提是它们也是可序列化的。
使用 Externalizable 进行序列化
此示例演示了 Externalizable 接口,它是 Serializable 的替代方案,提供了对序列化的完全控制。
package com.zetcode;
import java.io.*;
public class ExternalizableExample {
static class Product implements Externalizable {
private String name;
private double price;
public Product() {} // Required for Externalizable
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeDouble(price);
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
this.name = in.readUTF();
this.price = in.readDouble();
}
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + "}";
}
}
public static void main(String[] args) {
// Serialize
Product product = new Product("Laptop", 999.99);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("product.ser"))) {
oos.writeObject(product);
System.out.println("Serialized: " + product);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("product.ser"))) {
Product deserialized = (Product) ois.readObject();
System.out.println("Deserialized: " + deserialized);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Product 类实现了 Externalizable,在 writeExternal 和 readExternal 方法中定义了自定义序列化逻辑。
Externalizable 比 Serializable 提供更多的控制,但需要一个无参数构造函数和显式序列化逻辑,适合优化场景。
来源
本教程全面探讨了 Java Serializable 接口,涵盖了基本序列化、瞬态字段、版本控制和自定义处理。 它对于对象持久性至关重要。
作者
列出所有Java教程。