Java Externalizable 接口
最后修改时间:2025 年 4 月 16 日
java.io.Externalizable 接口为 Java 对象提供了自定义序列化控制。它扩展了 Serializable 接口,并允许完全控制序列化过程。类需要实现两个方法:writeExternal 和 readExternal。
Externalizable 与标准的 Java 序列化的不同之处在于,它赋予开发者完全负责写入和读取对象状态的责任。这使得可以优化序列化格式和版本控制。该接口对于性能至关重要的应用程序非常有用。
Externalizable 接口概述
Externalizable 接口定义了两个必须实现的方法。这些方法处理将对象状态写入流以及将其读回。该接口比默认序列化提供更多的控制。
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
上面的代码显示了 Externalizable 接口声明。writeExternal 将对象数据写入 ObjectOutput。readExternal 从 ObjectInput 读取数据以重建对象。
基本的 Externalizable 实现
这个例子演示了一个简单的类实现 Externalizable。该类精确地控制哪些字段被序列化以及如何序列化。这提供了比默认序列化更好的性能。
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 类使用自定义序列化实现了 Externalizable。password 字段被标记为 transient 并被排除。Externalizable 类需要一个公共的无参数构造函数。toString 方法有助于演示对象状态。
序列化和反序列化 Externalizable 对象
这个例子展示了如何序列化和反序列化一个 Externalizable 对象。该过程像标准序列化一样使用 ObjectOutputStream 和 ObjectInputStream。
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 提供了比标准序列化更好的版本控制。您可以手动处理类格式的不同版本。此示例演示了版本感知的序列化。
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 可以处理具有对其他对象引用的复杂对象图。每个被引用的对象也必须实现 Serializable 或 Externalizable。这个例子展示了一个包含 Employees 的 Department。
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 + "}";
}
}
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。这两个类都实现了 Externalizable。Department 写入其名称,然后写入每个员工。读取时,它首先读取员工计数,然后重建列表。这保持了对象图结构。
使用 Externalizable 进行性能优化
通过优化数据格式,Externalizable 可以显著提高序列化性能。这个例子展示了一个 3D 向量类的高性能实现。
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 需要仔细的安全实现。这个例子展示了使用加密安全处理敏感数据。
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 应用程序中使用自定义序列化至关重要。
作者
列出所有Java教程。