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

在上一篇文章“用示例介绍的有关Java序列化的一切”中 ,我解释了如何使用以下方法序列化/反序列化一个对象
Serializable接口,还说明了如何使用writeObjectreadObject方法自定义序列化过程。

Java序列化过程的缺点

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

默认的序列化过程是完全递归的。 因此,每当我们尝试序列化一个对象时,序列化过程都会尝试使用我们的类( staticstatic除外)对所有字段(原始和引用)进行序列化。
transient场)。 这使得序列化过程非常缓慢。

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

因此,如我们所见,使用默认序列化过程有很多弊端,例如:

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

什么是外部化和外部化接口

正如我们在上面看到的,默认的Java序列化效率不高。 我们可以通过使用Externalizable接口而不是
Serializable接口。

我们可以通过实现
可外部化的接口并覆盖它的方法writeExternal()
readExternal() 。 但是使用这种方法,我们将无法从JVM获得任何类型的默认序列化逻辑,而是由我们来提供完整的序列化和反序列化逻辑。

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

我们将以下面的Employee类对象为例进行说明:

 // 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.  Employee class 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." ); 
     }  } 

序列化如何与可外部化接口一起工作

如上面在示例Employee类中所见,我们可以通过实现Externalizable接口并覆盖其方法writeExternal()readExternal()来编写自己的序列化逻辑。

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

通过调用原始类型的DataInput方法和对象,字符串和数组的readObject方法,该对象可以实现readExternal方法以恢复其内容。 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(); 
         } 
     }  } 

Externalizable接口是Serializable的子接口,即
Externalizable extends Serializable 。 因此,如果我们实现Externalizable接口并覆盖其writeExternal()和 然后,将使用readExternal()方法优先于这些方法,而不是由JVM提供的默认序列化机制。 这些方法取代了writeObjectreadObject方法的自定义实现,因此,如果我们还提供writeObject()readObject() ,则将忽略它们。

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

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

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

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

Java 序列化还可以用于深度克隆对象 。 Java克隆是Java社区中最有争议的话题,它的确有其缺点,但是在对象完全满足Java克隆的强制条件之前,它仍然是创建对象副本的最流行和最简单的方法。 我在3篇文章的Java克隆系列中详细介绍了克隆 ,其中包括Java克隆和克隆类型(浅和深)等文章, 并带有示例Java克隆–复制构造器与克隆Java克隆–甚至复制构造器都不是如果您想了解更多有关克隆的知识,请充分阅读它们。

可外部化与可序列化之间的差异

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

您可以在此找到本文的完整源代码。
Github存储库 ,请随时提供宝贵的反馈。

翻译自: https://www.javacodegeeks.com/2019/08/customize-serialization-java-using-externalizable-interface.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值