Guide to the Externalizable Interface in Java – Java中的可外部化接口指南

最后修改: 2018年 3月 16日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll have a quick look at java’s java.io.Externalizable interface. The main goal of this interface is to facilitate custom serialization and deserialization.

在本教程中,我们将快速了解java的java.io.Externalizable接口。这个接口的主要目标是促进自定义序列化和反序列化。

Before we go ahead, make sure you check out the serialization in Java article. The next chapter is about how to serialize a Java object with this interface.

在我们继续之前,请确保你查看Java中的序列化文章。下一章将介绍如何用这个接口来序列化一个Java对象。

After that, we’re going to discuss the key differences compared to the java.io.Serializable interface.

之后,我们将讨论与java.io.Serializable接口相比的主要区别。

2. The Externalizable Interface

2.可外部化的界面

Externalizable extends from the java.io.Serializable marker interface. Any class that implements Externalizable interface should override the writeExternal(), readExternal() methods. That way we can change the JVM’s default serialization behavior.

Externalizable扩展自java.io.Serializable标记接口。任何实现Externalizable接口的类都应该覆盖writeExternal()readExternal()方法。这样我们就可以改变JVM的默认序列化行为。

2.1. Serialization

2.1.序列化

Let’s have a look at this simple example:

让我们看一下这个简单的例子。

public class Country implements Externalizable {
  
    private static final long serialVersionUID = 1L;
  
    private String name;
    private int code;
  
    // getters, setters
  
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(code);
    }
  
    @Override
    public void readExternal(ObjectInput in) 
      throws IOException, ClassNotFoundException {
        this.name = in.readUTF();
        this.code = in.readInt();
    }
}

Here, we’ve defined a class Country that implements the Externalizable interface and implements the two methods mentioned above.

在这里,我们定义了一个Country类,它实现了Externalizable接口并实现了上面提到的两个方法。

In the writeExternal() method, we’re adding the object’s properties to the ObjectOutput stream. This has standard methods like writeUTF() for String and writeInt() for the int values.

writeExternal()方法中,我们要将对象的属性添加到ObjectOutput流中。这有标准的方法,如writeUTF()用于StringwriteInt()用于int值。

Next, for deserializing the object, we’re reading from the ObjectInput stream using the readUTF(), readInt() methods to read the properties in the same exact order in which they were written.

接下来,对于反序列化对象,我们从ObjectInput流中读取,使用readUTF()、readInt()方法,按照与写入时完全相同的顺序读取属性。

It’s a good practice to add the serialVersionUID manually. If this is absent, the JVM will automatically add one.

手动添加serialVersionUID是个不错的做法。如果没有这个,JVM会自动添加一个。

The automatically generated number is compiler dependent. This means it may cause an unlikely InvalidClassException.

自动生成的数字是依赖于编译器的。这意味着它可能会引起一个不太可能的InvalidClassException

Let’s test the behavior we implemented above:

让我们测试一下我们上面实现的行为。

@Test
public void whenSerializing_thenUseExternalizable() 
  throws IOException, ClassNotFoundException {
       
    Country c = new Country();
    c.setCode(374);
    c.setName("Armenia");
   
    FileOutputStream fileOutputStream
     = new FileOutputStream(OUTPUT_FILE);
    ObjectOutputStream objectOutputStream
     = new ObjectOutputStream(fileOutputStream);
    c.writeExternal(objectOutputStream);
   
    objectOutputStream.flush();
    objectOutputStream.close();
    fileOutputStream.close();
   
    FileInputStream fileInputStream
     = new FileInputStream(OUTPUT_FILE);
    ObjectInputStream objectInputStream
     = new ObjectInputStream(fileInputStream);
   
    Country c2 = new Country();
    c2.readExternal(objectInputStream);
   
    objectInputStream.close();
    fileInputStream.close();
   
    assertTrue(c2.getCode() == c.getCode());
    assertTrue(c2.getName().equals(c.getName()));
}

In this example, we’re first creating a Country object and writing it to a file. Then, we’re deserializing the object from the file and verifying the values are correct.

在这个例子中,我们首先创建一个Country对象并将其写入一个文件。然后,我们从文件中反序列化该对象并验证其值是否正确。

The output of the printed c2 object:

打印的c2对象的输出。

Country{name='Armenia', code=374}

This shows we’ve successfully deserialized the object.

这表明我们已经成功地反序列化了该对象。

2.2. Inheritance

2.2.继承性

When a class inherits from the Serializable interface, the JVM automatically collects all the fields from sub-classes as well and makes them serializable.

当一个类继承自Serializable 接口时,JVM会自动从子类中收集所有字段,并使它们可被序列化。

Keep in mind that we can apply this to Externalizable as well. We just need to implement the read/write methods for every sub-class of the inheritance hierarchy.

请记住,我们也可以将此应用于Externalizable我们只需要为继承层次的每个子类实现读/写方法。

Let’s look at the Region class below which extends our Country class from the previous section:

让我们看看下面的Region类,它扩展了上一节的Country类。

public class Region extends Country implements Externalizable {
 
    private static final long serialVersionUID = 1L;
 
    private String climate;
    private Double population;
 
    // getters, setters
 
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        super.writeExternal(out);
        out.writeUTF(climate);
    }
 
    @Override
    public void readExternal(ObjectInput in) 
      throws IOException, ClassNotFoundException {
 
        super.readExternal(in);
        this.climate = in.readUTF();
    }
}

Here, we added two additional properties and serialized the first one.

在这里,我们添加了两个额外的属性,并将第一个属性序列化。

Note that we also called super.writeExternal(out), super.readExternal(in) within serializer methods to save/restore the parent class fields as well.

请注意,我们还在序列化器方法中调用了super.writeExternal(out), super.readExternal(in)来保存/恢复父类字段

Let’s run the unit test with the following data:

让我们用以下数据运行单元测试。

Region r = new Region();
r.setCode(374);
r.setName("Armenia");
r.setClimate("Mediterranean");
r.setPopulation(120.000);

Here’s the deserialized object:

这是被反序列化的对象。

Region{
  country='Country{
    name='Armenia',
    code=374}'
  climate='Mediterranean', 
  population=null
}

Notice that since we didn’t serialize the population field in Region class, the value of that property is null.

请注意,由于我们没有在Region类中序列化population字段,该属性的值是null./strong>。

3. Externalizable vs Serializable

3.可外置的可序列化的

Let’s go through the key differences between the two interfaces:

让我们来看看这两个界面的主要区别。

  • Serialization Responsibility

The key difference here is how we handle the serialization process. When a class implements the java.io.Serializable interface, the JVM takes full responsibility for serializing the class instance. In case of Externalizable, it’s the programmer who should take care of the whole serialization and also deserialization process.

这里的关键区别在于我们如何处理序列化过程。当一个类实现了java.io.Serializable接口时,JVM将完全负责序列化该类实例。Externalizable的情况下,应该由程序员来负责整个序列化和反序列化过程。

  • Use Case

If we need to serialize the entire object, the Serializable interface is a better fit. On the other hand, for custom serialization, we can control the process using Externalizable.

如果我们需要对整个对象进行序列化,那么Serializable接口就比较适合。另一方面,对于自定义序列化,我们可以使用Externalizable来控制这个过程。

  • Performance

The java.io.Serializable interface uses reflection and metadata which causes relatively slow performance. By comparison, the Externalizable interface gives you full control over the serialization process.

java.io.Serializable接口使用反射和元数据,导致性能相对较慢。相比之下,Externalizable接口让你完全控制序列化过程。

  • Reading Order

While using Externalizable, it’s mandatory to read all the field states in the exact order as they were written. Otherwise, we’ll get an exception.

在使用Externalizable时,必须按照写好的顺序读取所有字段的状态。否则,我们会得到一个异常。

For example, if we change the reading order of the code and name properties in the Country class, a java.io.EOFException will be thrown.

例如,如果我们改变codename属性在Country类中的读取顺序,将抛出java.io.EOFException

Meanwhile, the Serializable interface doesn’t have that requirement.

同时,Serializable接口并没有这个要求。

  • Custom Serialization

We can achieve custom serialization with the Serializable interface by marking the field with transient keyword. The JVM won’t serialize the particular field but it’ll add up the field to file storage with the default value. That’s why it’s a good practice to use Externalizable in case of custom serialization.

我们可以通过Serializable接口实现自定义序列化,即用transient关键字标记字段。JVM不会对特定的字段进行序列化,但它会将该字段以默认值添加到文件存储中。这就是为什么在自定义序列化的情况下,使用Externalizable是一个好的做法。

4. Conclusion

4.结论

In this short guide to the Externalizable interface, we discussed the key features, advantages and demonstrated examples of simple use. We also made a comparison with the Serializable interface.

在这篇关于Externalizable接口的简短指南中,我们讨论了主要特征、优势并演示了简单的使用实例。我们还与Serializable接口做了比较。

As usual, the full source code of the tutorial is available over on GitHub.

像往常一样,该教程的完整源代码可在GitHub上获得