ZetCode

Java Serializable 接口

最后修改时间:2025 年 4 月 16 日

java.io.Serializable 接口是一个标记接口,用于启用 Java 中的对象序列化。 序列化将对象转换为字节流以进行存储或传输。 反序列化从这些字节流中重建对象。

类实现 Serializable 来表明它们的实例可以被序列化。 该接口没有要实现的方法。 序列化会自动处理原始类型和对象图。 它保留对象引用和循环引用。

Serializable 接口概述

序列化需要实现 Serializable 接口。 该过程使用 ObjectOutputStreamObjectInputStream。 瞬态 (Transient) 字段从序列化中排除。 静态 (Static) 字段永远不会被序列化。

public interface Serializable {
    // Marker interface - no methods
}

上面的代码显示了 Serializable 的简单声明。 尽管是空的,但它启用了强大的序列化功能。 当实现此接口时,JVM 会自动处理所有序列化机制。

基本序列化示例

此示例演示了简单对象的基本序列化。 Person 类实现了 Serializable。 我们序列化到一个文件,然后反序列化回一个对象。

Main.java
import java.io.*;

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 class Main {
    public static void main(String[] args) {
        Person person = new Person("John Doe", 30);
        
        // Serialization
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("Serialized: " + person);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialization
        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();
        }
    }
}

此示例显示了 Person 对象的完整序列化和反序列化。 序列化数据被写入 "person.ser"。 反序列化后,对象的状态与序列化前相同。 请注意,在读取对象时需要强制转换。

瞬态字段示例

transient 关键字将字段从序列化中排除。 这对于敏感数据或不应持久化的字段很有用。 在反序列化期间,瞬态字段被设置为默认值。

Main.java
import java.io.*;

class Account implements Serializable {
    private String username;
    private transient String password; // Won't be serialized
    private double balance;
    
    public Account(String username, String password, double balance) {
        this.username = username;
        this.password = password;
        this.balance = balance;
    }
    
    @Override
    public String toString() {
        return "Account{username='" + username + "', password='" + 
            (password == null ? "null" : "****") + "', balance=" + balance + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Account account = new Account("johndoe", "secret123", 1000.50);
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("account.ser"))) {
            oos.writeObject(account);
            System.out.println("Serialized: " + account);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("account.ser"))) {
            Account deserialized = (Account) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了 transient 字段在序列化期间的行为。 密码字段从序列化中排除。 反序列化后,密码字段为 null。 这可以保护敏感数据免于以序列化形式存储。

使用 readObject 和 writeObject 进行自定义序列化

为了更好地控制序列化,类可以实现自定义方法。 writeObjectreadObject 允许自定义序列化逻辑。 这些方法必须是私有的并且具有特定的签名。

Main.java
import java.io.*;

class Employee implements Serializable {
    private String name;
    private transient double salary; // Custom serialization
    
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); // Serialize non-transient fields
        oos.writeDouble(salary * 1.1); // Custom serialization logic
    }
    
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject(); // Deserialize non-transient fields
        this.salary = ois.readDouble() / 1.1; // Custom deserialization
    }
    
    @Override
    public String toString() {
        return "Employee{name='" + name + "', salary=" + salary + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Employee emp = new Employee("Alice Smith", 50000);
        
        // Serialize
        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();
        }
    }
}

此示例显示了瞬态字段的自定义序列化。 薪水被标记为瞬态,但仍使用自定义逻辑进行序列化。 在序列化期间,薪水增加 10%。 在反序列化期间,它减少 10%。 这演示了如何实现自定义序列化逻辑。

SerialVersionUID 用于版本控制

serialVersionUID 是序列化类的版本控制机制。 它确保序列化对象和类版本之间的兼容性。 如果未定义,JVM 会根据类结构生成一个。

Main.java
import java.io.*;

class Product implements Serializable {
    private static final long serialVersionUID = 1L; // Version identifier
    private String name;
    private double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    @Override
    public String toString() {
        return "Product{name='" + name + "', price=" + price + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Product product = new Product("Laptop", 999.99);
        
        // Serialize
        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();
        }
    }
}

此示例演示了使用 serialVersionUID 进行版本控制。 该常量有助于在类更改时保持兼容性。 如果在反序列化期间 UID 不同,则会发生 InvalidClassException。 始终为重要的可序列化类显式声明 serialVersionUID。

继承和序列化

继承的序列化行为需要特别考虑。 如果超类是可序列化的,则其字段会自动序列化。 如果不是,则必须在序列化期间手动处理其字段。

Main.java
import java.io.*;

class Address {
    private String city;
    private String country;
    
    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
    
    @Override
    public String toString() {
        return city + ", " + country;
    }
}

class Customer extends Address implements Serializable {
    private String name;
    private transient Address address; // Non-serializable superclass
    
    public Customer(String name, String city, String country) {
        super(city, country);
        this.name = name;
        this.address = new Address(city, country);
    }
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.toString()); // Serialize superclass state
    }
    
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        String addrStr = (String) ois.readObject();
        String[] parts = addrStr.split(", ");
        this.address = new Address(parts[0], parts[1]);
    }
    
    @Override
    public String toString() {
        return "Customer{name='" + name + "', address=" + address + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Customer customer = new Customer("Bob", "New York", "USA");
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("customer.ser"))) {
            oos.writeObject(customer);
            System.out.println("Serialized: " + customer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("customer.ser"))) {
            Customer deserialized = (Customer) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例显示了如何处理继承的序列化。 Address 超类不可序列化,因此我们手动处理它。 Customer 类为超类字段实现了自定义序列化。 这确保了所有必要的数据都得到正确地序列化和反序列化。

序列化集合

如果 Java 集合的元素是可序列化的,则可以序列化它们。 这包括 ArrayListHashMap 和其他标准集合。 整个集合结构在序列化期间得以保留。

Main.java
import java.io.*;
import java.util.ArrayList;
import java.util.List;

class Student implements Serializable {
    private String name;
    private int id;
    
    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }
    
    @Override
    public String toString() {
        return "Student{name='" + name + "', id=" + id + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 101));
        students.add(new Student("Bob", 102));
        students.add(new Student("Charlie", 103));
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("students.ser"))) {
            oos.writeObject(students);
            System.out.println("Serialized: " + students);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("students.ser"))) {
            @SuppressWarnings("unchecked")
            List<Student> deserialized = (List<Student>) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了序列化 Student 对象的 ArrayList。 整个集合结构得以保留。 每个元素都必须实现 Serializable。 反序列化的集合与原始集合相同,保持所有元素和顺序。

来源

Java Serializable 接口文档

在本文中,我们介绍了 Java Serializable 接口的基本方面。 理解序列化对于在 Java 应用程序中持久化对象并在网络上传输它们至关重要。

作者

我叫 Jan Bodnar,是一位敬业的程序员,在该领域拥有多年的经验。 我于 2007 年开始撰写编程文章,此后撰写了超过 1,400 篇文章和八本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程