ZetCode

Java Externalizable 接口

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

java.io.Externalizable 接口为 Java 对象提供了自定义序列化控制。它扩展了 Serializable 接口,并允许完全控制序列化过程。类需要实现两个方法:writeExternalreadExternal

Externalizable 与标准的 Java 序列化的不同之处在于,它赋予开发者完全负责写入和读取对象状态的责任。这使得可以优化序列化格式和版本控制。该接口对于性能至关重要的应用程序非常有用。

Externalizable 接口概述

Externalizable 接口定义了两个必须实现的方法。这些方法处理将对象状态写入流以及将其读回。该接口比默认序列化提供更多的控制。

public interface Externalizable extends Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

上面的代码显示了 Externalizable 接口声明。writeExternal 将对象数据写入 ObjectOutputreadExternalObjectInput 读取数据以重建对象。

基本的 Externalizable 实现

这个例子演示了一个简单的类实现 Externalizable。该类精确地控制哪些字段被序列化以及如何序列化。这提供了比默认序列化更好的性能。

Person.java
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable {
    private String name;
    private int age;
    private transient String password; // Not serialized
    
    public Person() {} // Required public no-arg constructor
    
    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
        // password is not written
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        age = in.readInt();
        password = "default"; // Set default value
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + 
               ", password='" + password + "'}";
    }
}

这个 Person 类使用自定义序列化实现了 Externalizablepassword 字段被标记为 transient 并被排除。Externalizable 类需要一个公共的无参数构造函数。toString 方法有助于演示对象状态。

序列化和反序列化 Externalizable 对象

这个例子展示了如何序列化和反序列化一个 Externalizable 对象。该过程像标准序列化一样使用 ObjectOutputStreamObjectInputStream

Main.java
import java.io.*;

public class Main {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30, "secret123");
        
        // Serialize
        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();
        }
    }
}

这段代码将一个 Person 对象序列化到一个文件并将其读回。输出显示了序列化之前和反序列化之后的对象状态。请注意,在反序列化期间,password 字段被重置为 "default",因为它没有被保存。try-with-resources 确保了正确的流处理。

使用 Externalizable 进行版本控制

Externalizable 提供了比标准序列化更好的版本控制。您可以手动处理类格式的不同版本。此示例演示了版本感知的序列化。

Product.java
import java.io.*;

public class Product implements Externalizable {
    private static final long serialVersionUID = 1L;
    private static final int CURRENT_VERSION = 2;
    
    private String name;
    private double price;
    private int quantity; // Added in version 2
    
    public Product() {}
    
    public Product(String name, double price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(CURRENT_VERSION);
        out.writeUTF(name);
        out.writeDouble(price);
        out.writeInt(quantity);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        int version = in.readInt();
        name = in.readUTF();
        price = in.readDouble();
        
        if (version >= 2) {
            quantity = in.readInt();
        } else {
            quantity = 1; // Default for old versions
        }
    }
    
    @Override
    public String toString() {
        return "Product{name='" + name + "', price=" + price + 
               ", quantity=" + quantity + "}";
    }
}

这个 Product 类演示了版本控制。CURRENT_VERSION 常量跟踪序列化格式版本。读取时,通过为新字段提供默认值来处理旧版本。这种方法保持了与旧序列化数据的向后兼容性。

具有 Externalizable 的复杂对象图

Externalizable 可以处理具有对其他对象引用的复杂对象图。每个被引用的对象也必须实现 SerializableExternalizable。这个例子展示了一个包含 Employees 的 Department。

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

public class Department implements Externalizable {
    private String name;
    private List<Employee> employees;
    
    public Department() {
        employees = new ArrayList<>();
    }
    
    public Department(String name) {
        this();
        this.name = name;
    }
    
    public void addEmployee(Employee emp) {
        employees.add(emp);
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(employees.size());
        for (Employee emp : employees) {
            out.writeObject(emp);
        }
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        int size = in.readInt();
        employees = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            employees.add((Employee) in.readObject());
        }
    }
    
    @Override
    public String toString() {
        return "Department{name='" + name + "', employees=" + employees + "}";
    }
}
Employee.java
import java.io.*;

public class Employee implements Externalizable {
    private String name;
    private int id;
    
    public Employee() {}
    
    public Employee(String name, int id) {
        this.name = name;
        this.id = id;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(id);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        id = in.readInt();
    }
    
    @Override
    public String toString() {
        return "Employee{name='" + name + "', id=" + id + "}";
    }
}

这个例子展示了一个包含多个 Employee 对象的 Department。这两个类都实现了 ExternalizableDepartment 写入其名称,然后写入每个员工。读取时,它首先读取员工计数,然后重建列表。这保持了对象图结构。

使用 Externalizable 进行性能优化

通过优化数据格式,Externalizable 可以显著提高序列化性能。这个例子展示了一个 3D 向量类的高性能实现。

Vector3D.java
import java.io.*;

public class Vector3D implements Externalizable {
    private float x, y, z;
    
    public Vector3D() {}
    
    public Vector3D(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // Write as single byte array for compactness
        byte[] data = new byte[12];
        intToBytes(Float.floatToIntBits(x), data, 0);
        intToBytes(Float.floatToIntBits(y), data, 4);
        intToBytes(Float.floatToIntBits(z), data, 8);
        out.write(data);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        byte[] data = new byte[12];
        in.readFully(data);
        x = Float.intBitsToFloat(bytesToInt(data, 0));
        y = Float.intBitsToFloat(bytesToInt(data, 4));
        z = Float.intBitsToFloat(bytesToInt(data, 8));
    }
    
    private void intToBytes(int value, byte[] dest, int offset) {
        dest[offset] = (byte)(value >> 24);
        dest[offset+1] = (byte)(value >> 16);
        dest[offset+2] = (byte)(value >> 8);
        dest[offset+3] = (byte)value;
    }
    
    private int bytesToInt(byte[] src, int offset) {
        return ((src[offset] & 0xFF) << 24) | 
               ((src[offset+1] & 0xFF) << 16) | 
               ((src[offset+2] & 0xFF) << 8) | 
               (src[offset+3] & 0xFF);
    }
    
    @Override
    public String toString() {
        return String.format("Vector3D(%.2f, %.2f, %.2f)", x, y, z);
    }
}

这个 Vector3D 类演示了性能优化。它没有写入三个单独的浮点数值,而是将它们打包成一个紧凑的字节数组。这减少了序列化开销和存储空间。辅助方法处理浮点数/整数和字节表示之间的转换。

使用 Externalizable 的安全注意事项

由于您控制序列化过程,Externalizable 需要仔细的安全实现。这个例子展示了使用加密安全处理敏感数据。

SecureData.java
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;

public class SecureData implements Externalizable {
    private static final String ALGORITHM = "AES";
    private static final byte[] KEY = "MySuperSecretKey".getBytes();
    
    private String sensitiveData;
    
    public SecureData() {}
    
    public SecureData(String sensitiveData) {
        this.sensitiveData = sensitiveData;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            
            byte[] encrypted = cipher.doFinal(sensitiveData.getBytes());
            out.writeUTF(Base64.getEncoder().encodeToString(encrypted));
        } catch (Exception e) {
            throw new IOException("Encryption failed", e);
        }
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        try {
            String encryptedStr = in.readUTF();
            byte[] encrypted = Base64.getDecoder().decode(encryptedStr);
            
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            
            byte[] decrypted = cipher.doFinal(encrypted);
            sensitiveData = new String(decrypted);
        } catch (Exception e) {
            throw new IOException("Decryption failed", e);
        }
    }
    
    public String getSensitiveData() {
        return sensitiveData;
    }
}

这个 SecureData 类演示了安全序列化。敏感数据在写入之前被加密,并在读取之后被解密。请注意,在实际应用程序中,应该正确保护加密密钥。该示例使用 Base64 编码来安全传输二进制数据。

来源

Java Externalizable 接口文档

在本文中,我们介绍了 Java Externalizable 接口的基本方法和特性。 理解这些概念对于在 Java 应用程序中使用自定义序列化至关重要。

作者

我叫 Jan Bodnar,是一位经验丰富的专业程序员。我于 2007 年开始撰写编程文章,至今已撰写超过 1400 篇文章和 8 本电子书。凭借超过 8 年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程