如何通过使用可外部化的接口在Java中自定义序列化

In a previous article Everything About Java Serialization Explained With Example, I explained how we can serialize/deserialize one object using Serializable interface and also explain how we can customise the serialization process using writeObject and readObject methods.

Disadvantages Of Java Serialization Process

但是,这些定制还不够,因为JVM可以完全控制序列化过程,而这些定制逻辑只是默认序列化过程的补充。 我们仍然必须通过调用默认的序列化逻辑ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()从writeObject和readObject方法。 而且,如果不调用这些默认方法,我们的对象将不会被序列化/反序列化。

默认的序列化过程是完全递归的。 因此,每当我们尝试序列化一个对象时,序列化过程就会尝试使用我们的类(除静态的和短暂的字段)。 这使得序列化过程非常缓慢。

现在,假设我们有一个对象,其中包含很多字段,由于某些原因,我们不想序列化这些字段(这些字段将始终分配有默认值)。 使用默认的序列化过程,我们将必须使所有这些字段都是瞬态的,但是它仍然不会高效,因为会进行大量检查以查看这些字段是否为瞬态的。

因此,正如我们所看到的,使用默认序列化过程有很多缺点,例如:

  1. 序列化的定制是不够的,因为JVM可以完全控制序列化过程,而我们的定制逻辑只是默认序列化过程的补充。默认序列化过程是完全递归且缓慢的。为了不对字段进行序列化,我们必须声明它为瞬态,而很多瞬态字段将再次使过程变慢。我们无法控制如何对字段进行序列化和反序列化。默认序列化过程在创建对象时不会调用构造函数,因此它无法调用构造函数提供的初始化逻辑。

What Is Externalization And Externalizable Interface

正如我们在上面看到的,默认的Java序列化效率不高。 我们可以通过使用以下方法解决其中一些问题可外部化接口代替可序列化接口。

We can write your own serialization logic by implementing the Externalizable interface and overriding it’s methods writeExternal() and readExternal(). But with this approach, we will not get any kind of default serialization logic from JVM and it is up to us to provide the complete serialization and deserialization logic.

因此,非常仔细地对测试这些方法进行编码非常必要,因为这可能会破坏序列化过程。 但是,如果正确实现,与默认序列化过程相比,外部化过程非常快。

我们将在下面使用雇员类对象作为说明的示例:

// Using Externalizable, complete serialization/deserialization logic becomes our responsibility,
// We need to tell what to serialize using writeExternal() method and what to deserialize using readExternal(),
// We can even serialize/deserialize static and transient variables,
// With implementation of writeExternal() and readExternal(),  methods writeObject() and readObject() becomes redundant and they do not get called.
class Employee implements Externalizable {

   // This serialVersionUID field is necessary for Serializable as well as Externalizable to provide version control,
    // Compiler will provide this field if we do not provide it which might change if we modify class structure of our class, and we will get InvalidClassException,
    // If we provide a value to this field and do not change it, serialization-deserialization will not fail if we change our class structure.
    private static final long serialVersionUID = 2L;

    private String firstName;
    private transient String lastName; // Using Externalizable, we can even serialize/deserialize transient variables, so declaring fields transient becomes unnecessary.
    private int age;
    private static String department; // Using Externalizable, we can even serialize/deserialize static variables according to our need.


    // Mandatory to have to make our class Externalizable
    // When an Externalizable object is reconstructed, the object is created using public no-arg constructor before the readExternal method is called.
    // If a public no-arg constructor is not present then a InvalidClassException is thrown at runtime.
    public Employee() {
    }

    // All-arg constructor to create objects manually
    public Employee(String firstName, String lastName, int age, String department) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        Employee.department = department;

        validateAge();
    }

    private void validateAge() {
        System.out.println("Validating age.");

        if (age < 18 || age > 70) {
            throw new IllegalArgumentException("Not a valid age to create an employee");
        }
    }

    @Override
    // We need to tell what to serialize in writeExternal() method
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Custom externalizable serialization logic invoked.");

        out.writeUTF(firstName);
        out.writeUTF(lastName);
        out.writeInt(age);
        out.writeUTF(department);
    }

    @Override
    // We need to tell what to deserialize in readExternal() method
    // The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal
    public void readExternal(ObjectInput in) throws IOException {
        System.out.println("Custom externalizable serialization logic invoked.");

        firstName = in.readUTF();
        lastName = in.readUTF();
        age = in.readInt();
        department = in.readUTF();

        validateAge();
    }

    @Override
    public String toString() {
        return String.format("Employee {firstName='%s', lastName='%s', age='%s', department='%s'}", firstName, lastName, age, department);
    }

    // Custom serialization logic, It will be called only if we have implemented Serializable instead of Externalizable.
    private void writeObject(ObjectOutputStream oos) throws IOException {
        System.out.println("Custom serialization logic invoked.");
    }

    // Custom deserialization logic, It will be called only if we have implemented Serializable instead of Externalizable.
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("Custom deserialization logic invoked.");
    }
}

How Serialization works with Externalizable Interface

As we can see above in our example Employee class, we can write your own serialization logic by implementing the Externalizable interface and overriding its methods writeExternal() and readExternal().

通过调用DataOutput的原始值对象或调用ObjectOutput的writeObject方法对象,字符串和数组,对象可以实现writeExternal方法来保存其内容。

该对象可以实现readExternal方法来恢复其内容,方法是针对原始类型调用DataInput方法,对于对象,字符串和数组调用readObject方法。 readExternal方法必须按与writeExternal相同的顺序和相同的类型读取值。

// We need to tell what fields to serialize in writeExternal() method
public void writeExternal(ObjectOutput out) throws IOException {
    System.out.println("Custom externalizable serialization logic invoked.");

    out.writeUTF(firstName);
    out.writeUTF(lastName);
    out.writeInt(age);
    out.writeUTF(department);
}

// We need to tell what fields to deserialize in readExternal() method
// The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal
public void readExternal(ObjectInput in) throws IOException {
    System.out.println("Custom externalizable serialization logic invoked.");

    firstName = in.readUTF();
    lastName = in.readUTF();
    age = in.readInt();
    department = in.readUTF();

    validateAge();
}

要将对象序列化和反序列化为文件,我们需要遵循与Serializable示例相同的过程,这意味着调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()如以下代码所示:

public class ExternalizableExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee empObj = new Employee("Shanti", "Sharma", 25, "IT");
        System.out.println("Object before serialization  => " + empObj.toString());

        // Serialization
        serialize(empObj);

        // Deserialization
        Employee deserializedEmpObj = deserialize();
        System.out.println("Object after deserialization => " + deserializedEmpObj.toString());
    }

    // Serialization code
    static void serialize(Employee empObj) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("data.obj");
             ObjectOutputStream oos = new ObjectOutputStream(fos))
        {
            oos.writeObject(empObj);
        }
    }

    // Deserialization code
    static Employee deserialize() throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream("data.obj");
             ObjectInputStream ois = new ObjectInputStream(fis))
        {
            return (Employee) ois.readObject();
        }
    }
}

的可外部化interface是的子接口可序列化即可外部化 extends 可序列化。 所以如果我们实施可外部化接口并覆盖其writeExternal()和readExternal()然后,将优先选择这些方法,而不是使用JVM提供的默认序列化机制。 这些方法取代了writeObject和readObject方法,所以如果我们还提供writeObject()和readObject()那么它们将被忽略。

在序列化过程中,将针对要序列化的每个对象的Externalizable接口进行测试。 如果对象支持Externalizable,则调用writeExternal方法。 如果对象不支持Externalizable并且实现了Serializable,则使用ObjectOutputStream保存该对象。

重建Externalizable对象时,将使用公共no-arg构造函数创建一个实例,然后调用readExternal方法。 可序列化的对象通过从ObjectInputStream读取来恢复。

重建Externizable对象时,在调用readExternal方法之前,使用公共的无参数构造函数创建对象。 如果不存在公共的无参数构造函数,则在运行时引发InvalidClassException。使用Externalizable,我们甚至可以序列化/反序列化瞬态变量,因此无需声明字段瞬态。使用Externalizable,我们甚至可以根据需要对静态变量进行序列化/反序列化。

Externalizable实例可以通过Serializable接口中记录的writeReplace和readResolve方法指定替换对象。

Java serialization can also be used to deep clone an object. Java cloning is the most debatable topic in Java community and it surely does have its drawbacks but it is still the most popular and easy way of creating a copy of an object until that object is full filling mandatory conditions of Java cloning. I have covered cloning in details in a 3 article long Ĵava Cloning Series which includes articles like Ĵava Cloning And Types Of Cloning (Shallow And Deep) In Details With Example, Ĵava Cloning - Copy Constructor Versus Cloning, Ĵava Cloning - Even Copy Constructors Are Not Sufficient, go ahead and read them if you want to know more about cloning.

Differences between Externalizable vs Serializable

让我们列出Java中Externalizable接口和Serializable接口之间的主要区别。

You can find the complete source code for this article on this Github Repository and please feel free to provide your valuable feedback.

from: https://dev.to//njnareshjoshi/how-to-customize-serialization-in-java-by-using-externalizable-interface-3gja

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值