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教程。