ZetCode

Java Serializable 接口

上次修改:2025 年 4 月 26 日

Java 中的 Serializable 接口使对象能够被转换为字节流以便存储或传输。它不需要实现任何方法,充当一个标记接口。

序列化对于将对象持久化到文件、数据库或通过网络发送它们至关重要。Serializable 有助于在各种应用程序中保存和恢复对象状态。

Serializable 接口概述

Serializable 接口是 java.io 包的一部分,是一个标记接口,表明一个类有资格进行序列化。 标记接口是一种特殊的接口,不包含任何方法或字段,但用作 Java 运行时环境关于类功能的信号。 当一个类实现 Serializable 接口时,它的对象可以使用 ObjectOutputStream 转换为字节流。 然后,可以将此字节流保存到文件中、通过网络传输或用于其他需要保存对象状态的上下文中。

反序列化是一个相反的过程,先前序列化的字节流被读取并使用 ObjectInputStream 重构为对象。这允许对象在不同的执行或环境中保持其状态和结构,从而实现持久存储或数据传输。

在设计用于序列化的类时,必须考虑以下几个因素,以确保健壮和安全的行为

总的来说,Serializable 接口在实现对象持久性和进程间通信方面起着至关重要的作用。 但是,开发人员必须谨慎行事并遵循最佳实践,以确保应用程序中高效、安全和向后兼容的序列化。

基本序列化和反序列化

此示例演示了实现 Serializable 的简单类的基本序列化和反序列化,将对象保存并加载到文件。

BasicSerialization.java
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();
        }
    }
}

此程序定义了一个实现 SerializablePerson 类。它将实例序列化到文件,然后将其反序列化回来。

try-with-resources 确保流被正确关闭。 输出确认反序列化的对象与原始对象匹配,证明了基本的序列化。

使用瞬态字段

此示例演示了如何使用 transient 关键字从序列化中排除字段,这对于敏感或不可序列化的数据非常有用。

TransientFields.java
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 来管理序列化期间的类版本控制,从而防止兼容性问题。

SerialVersionUID.java
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 是在类结构随时间演变时保持序列化兼容性的最佳实践。

自定义序列化

此示例演示了如何通过实现 writeObjectreadObject 方法来自定义序列化,以便特殊处理对象状态。

CustomSerialization.java
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。

自定义序列化对于转换数据、处理瞬态字段或确保与对象持久性期间的旧系统兼容非常有用。

序列化复杂对象

此示例演示了序列化对其他可序列化对象具有引用的对象,展示了如何在序列化中处理对象图。

ComplexSerialization.java
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 的替代方案,提供了对序列化的完全控制。

ExternalizableExample.java
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,在 writeExternalreadExternal 方法中定义了自定义序列化逻辑。

ExternalizableSerializable 提供更多的控制,但需要一个无参数构造函数和显式序列化逻辑,适合优化场景。

来源

Java Serializable 文档

本教程全面探讨了 Java Serializable 接口,涵盖了基本序列化、瞬态字段、版本控制和自定义处理。 它对于对象持久性至关重要。

作者

我是 Jan Bodnar,一位充满激情的程序员,拥有丰富的经验。 自 2007 年以来,我撰写了 1,400 多篇文章和 8 本电子书。 拥有超过 8 年的教学经验,我致力于分享知识并帮助他人学习编程概念。

列出所有Java教程