ZetCode

Java Externalizable 接口

上次修改:2025 年 4 月 26 日

Java 中的 Externalizable 接口提供了对对象序列化和反序列化的完全控制。与 Serializable 不同,它需要显式地实现序列化逻辑。

Externalizable 适用于优化序列化、处理复杂对象或确保跨系统的兼容性。它非常适合需要精细化持久化或网络传输的应用程序。

Externalizable 接口概述

Externalizable 接口是 java.io 包的一部分,它扩展了 Serializable。它强制实现 writeExternalreadExternal 方法以进行自定义序列化。

使用 Externalizable 的类必须提供一个无参数构造函数,因为反序列化会在调用 readExternal 之前创建一个实例。 这提供了比 Serializable 更大的灵活性。

基本的 Externalizable 实现

此示例演示了 Externalizable 的基本实现,以序列化和反序列化一个简单的对象,并将其保存到文件中。

BasicExternalizable.java
package com.zetcode;

import java.io.*;

public class BasicExternalizable {

    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 {
            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,定义了自定义的序列化逻辑。 无参数构造函数对于反序列化至关重要。

此示例展示了 Externalizable 如何精确控制要序列化的字段,从而确保高效的对象持久化。

序列化复杂对象

此示例说明了如何使用 Externalizable 序列化一个引用其他对象的复杂对象,从而管理整个对象图。

ComplexExternalizable.java
package com.zetcode;

import java.io.*;

public class ComplexExternalizable {

    static class Department implements Externalizable {
        private String name;

        public Department() {}

        public Department(String name) {
            this.name = name;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.name = in.readUTF();
        }

        @Override
        public String toString() {
            return "Department{name='" + name + "'}";
        }
    }

    static class Employee implements Externalizable {
        private String name;
        private Department department;

        public Employee() {}

        public Employee(String name, Department department) {
            this.name = name;
            this.department = department;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeObject(department);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.name = in.readUTF();
            this.department = (Department) in.readObject();
        }

        @Override
        public String toString() {
            return "Employee{name='" + name + "', department=" + department + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Department dept = new Department("Engineering");
        Employee emp = new Employee("Alice", dept);
        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 类引用了一个 Department 对象,两者都实现了 Externalizable。自定义逻辑处理对象图序列化。

这演示了 Externalizable 如何管理复杂的关系,从而可以精确控制引用对象的序列化。

优化序列化大小

此示例显示了如何使用 Externalizable 通过选择性地序列化字段或压缩数据来优化序列化大小。

OptimizedExternalizable.java
package com.zetcode;

import java.io.*;

public class OptimizedExternalizable {

    static class Book implements Externalizable {
        private String title;
        private String author;
        private transient String description; // Not serialized

        public Book() {}

        public Book(String title, String author, String description) {
            this.title = title;
            this.author = author;
            this.description = description;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(title);
            out.writeUTF(author);
            // Skip description to reduce size
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.title = in.readUTF();
            this.author = in.readUTF();
            this.description = "N/A"; // Default value
        }

        @Override
        public String toString() {
            return "Book{title='" + title + "', author='" + author + "', description='" + description + "'}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Book book = new Book("Java Guide", "John Doe", "Comprehensive Java tutorial");
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("book.ser"))) {
            oos.writeObject(book);
            System.out.println("Serialized: " + book);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("book.ser"))) {
            Book deserialized = (Book) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Book 类从序列化中排除了 description 字段,从而减小了序列化数据的大小。 在反序列化期间设置默认值。

此优化对于带宽或存储空间受限的应用程序非常有用,展示了 Externalizable 在数据管理方面的灵活性。

处理版本控制

此示例演示了 Externalizable 如何通过实现支持向后兼容的逻辑来处理类版本控制。

VersionedExternalizable.java
package com.zetcode;

import java.io.*;

public class VersionedExternalizable {

    static class User implements Externalizable {
        private static final long serialVersionUID = 1L;
        private String username;
        private int version = 1; // Version control
        private String email; // Added in version 2

        public User() {}

        public User(String username, String email) {
            this.username = username;
            this.email = email;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(version);
            out.writeUTF(username);
            if (version >= 2) {
                out.writeUTF(email);
            }
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.version = in.readInt();
            this.username = in.readUTF();
            if (version >= 2) {
                this.email = in.readUTF();
            } else {
                this.email = "unknown@example.com"; // Default for older versions
            }
        }

        @Override
        public String toString() {
            return "User{username='" + username + "', email='" + email + "', version=" + version + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        User user = new User("bob", "bob@example.com");
        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 类使用版本字段来管理兼容性。 它根据版本有条件地序列化 email 字段。

这种方法确保了向后兼容性,从而可以在支持更新类定义中的新字段的同时,反序列化较旧的对象版本。

带有不可序列化字段的 Externalizable

此示例展示了如何在 Externalizable 类中通过手动管理序列化来处理不可序列化的字段。

NonSerializableFields.java
package com.zetcode;

import java.io.*;

public class NonSerializableFields {

    static class Logger {
        private String logLevel;

        public Logger(String logLevel) {
            this.logLevel = logLevel;
        }

        public String getLogLevel() {
            return logLevel;
        }
    }

    static class Application implements Externalizable {
        private String appName;
        private transient Logger logger;

        public Application() {}

        public Application(String appName, Logger logger) {
            this.appName = appName;
            this.logger = logger;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(appName);
            out.writeUTF(logger.getLogLevel());
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.appName = in.readUTF();
            this.logger = new Logger(in.readUTF());
        }

        @Override
        public String toString() {
            return "Application{appName='" + appName + "', loggerLevel='" + logger.getLogLevel() + "'}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Logger logger = new Logger("INFO");
        Application app = new Application("MyApp", logger);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("app.ser"))) {
            oos.writeObject(app);
            System.out.println("Serialized: " + app);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("app.ser"))) {
            Application deserialized = (Application) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Application 类包含一个不可序列化的 Logger 对象。 自定义序列化逻辑处理其 logLevel 字段。

这演示了 Externalizable 如何通过显式序列化其相关数据来管理不可序列化的字段,从而确保正确的对象恢复。

Externalizable 与 Serializable

此示例通过使用两种方法序列化同一对象来比较 ExternalizableSerializable,从而突出显示它们之间的差异。

ExternalizableVsSerializable.java
package com.zetcode;

import java.io.*;

public class ExternalizableVsSerializable {

    static class ItemSerializable implements Serializable {
        private String name;
        private int quantity;

        public ItemSerializable(String name, int quantity) {
            this.name = name;
            this.quantity = quantity;
        }

        @Override
        public String toString() {
            return "ItemSerializable{name='" + name + "', quantity=" + quantity + "}";
        }
    }

    static class ItemExternalizable implements Externalizable {
        private String name;
        private int quantity;

        public ItemExternalizable() {}

        public ItemExternalizable(String name, int quantity) {
            this.name = name;
            this.quantity = quantity;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeInt(quantity);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.name = in.readUTF();
            this.quantity = in.readInt();
        }

        @Override
        public String toString() {
            return "ItemExternalizable{name='" + name + "', quantity=" + quantity + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize Serializable
        ItemSerializable itemSer = new ItemSerializable("Pen", 100);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("itemSer.ser"))) {
            oos.writeObject(itemSer);
            System.out.println("Serialized Serializable: " + itemSer);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Serialize Externalizable
        ItemExternalizable itemExt = new ItemExternalizable("Pen", 100);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("itemExt.ser"))) {
            oos.writeObject(itemExt);
            System.out.println("Serialized Externalizable: " + itemExt);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize Serializable
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("itemSer.ser"))) {
            ItemSerializable deserialized = (ItemSerializable) ois.readObject();
            System.out.println("Deserialized Serializable: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        // Deserialize Externalizable
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("itemExt.ser"))) {
            ItemExternalizable deserialized = (ItemExternalizable) ois.readObject();
            System.out.println("Deserialized Externalizable: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

两个类序列化相同的数据:一个使用 Serializable,另一个使用 ExternalizableExternalizable 需要显式的序列化逻辑。

Externalizable 提供了更多的控制和效率,但需要手动实现,而 Serializable 更简单但灵活性较差。

性能注意事项

此示例比较了 ExternalizableSerializable 的性能,突出显示了自定义序列化的效率。

PerformanceExternalizable.java
package com.zetcode;

import java.io.*;

public class PerformanceExternalizable {

    static class DataSerializable implements Serializable {
        private String data;
        private int value;

        public DataSerializable(String data, int value) {
            this.data = data;
            this.value = value;
        }
    }

    static class DataExternalizable implements Externalizable {
        private String data;
        private int value;

        public DataExternalizable() {}

        public DataExternalizable(String data, int value) {
            this.data = data;
            this.value = value;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(data);
            out.writeInt(value);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.data = in.readUTF();
            this.value = in.readInt();
        }
    }

    public static void main(String[] args) {
        final int COUNT = 10000;

        // Test Serializable
        long start = System.currentTimeMillis();
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("dataSer.ser"))) {
            for (int i = 0; i < COUNT; i++) {
                oos.writeObject(new DataSerializable("Test", i));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long duration = System.currentTimeMillis() - start;
        System.out.println("Serializable time: " + duration + "ms");

        // Test Externalizable
        start = System.currentTimeMillis();
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("dataExt.ser"))) {
            for (int i = 0; i < COUNT; i++) {
                oos.writeObject(new DataExternalizable("Test", i));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        duration = System.currentTimeMillis() - start;
        System.out.println("Externalizable time: " + duration + "ms");
    }
}

该程序测量 SerializableExternalizable 的序列化时间。 由于其最小的开销,Externalizable 通常更快。

对于性能至关重要的应用程序,请使用 Externalizable,但要考虑实现自定义序列化逻辑的额外复杂性。

来源

Java Externalizable 文档

本教程全面探讨了 Java Externalizable 接口,涵盖了基本用法、复杂对象、优化和版本控制。 它是自定义序列化的关键。

作者

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

列出所有Java教程