Guía de la interfaz externalizable en Java

1. Introducción

En este tutorial, veremos rápidamente la interfaz java.io.Externalizable de java . El objetivo principal de esta interfaz es facilitar la serialización y deserialización personalizadas.

Antes de continuar, asegúrese de consultar el artículo sobre serialización en Java. El siguiente capítulo trata sobre cómo serializar un objeto Java con esta interfaz.

Después de eso, vamos a discutir las diferencias clave en comparación con la interfaz java.io.Serializable .

2. La interfaz externalizable

Externalizable se extiende desde la interfaz de marcador java.io.Serializable . Cualquier clase que implementa Externalizable interfaz debe anular los writeExternal () , readExternal () métodos . De esa forma podemos cambiar el comportamiento de serialización predeterminado de la JVM.

2.1. Publicación por entregas

Echemos un vistazo a este sencillo ejemplo:

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(); } }

Aquí, hemos definido una clase Country que implementa la interfaz Externalizable e implementa los dos métodos mencionados anteriormente.

En el método writeExternal () , estamos agregando las propiedades del objeto a la secuencia ObjectOutput . Esto tiene métodos estándar como writeUTF () para String y writeInt () para los valores int.

A continuación, para deserializar el objeto, estamos leyendo del flujo ObjectInput usando los métodos readUTF (), readInt () para leer las propiedades en el mismo orden exacto en el que fueron escritas.

Es una buena práctica agregar el serialVersionUID manualmente. Si no está, la JVM agregará uno automáticamente.

El número generado automáticamente depende del compilador. Esto significa que puede causar una excepción InvalidClassException poco probable .

Probemos el comportamiento que implementamos anteriormente:

@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())); }

En este ejemplo, primero estamos creando un objeto Country y escribiéndolo en un archivo. Luego, estamos deserializando el objeto del archivo y verificando que los valores sean correctos.

La salida del objeto c2 impreso :

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

Esto muestra que deserializamos correctamente el objeto.

2.2. Herencia

Cuando una clase hereda de la interfaz serializable , la JVM también recopila automáticamente todos los campos de las subclases y los hace serializables.

Tenga en cuenta que también podemos aplicar esto a Externalizable . Solo necesitamos implementar los métodos de lectura / escritura para cada subclase de la jerarquía de herencia.

Veamos la clase Región a continuación, que extiende nuestra clase País de la sección anterior:

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(); } }

Aquí, agregamos dos propiedades adicionales y serializamos la primera.

Tenga en cuenta que también llamamos super.writeExternal (out), super.readExternal (in) dentro de los métodos del serializador para guardar / restaurar los campos de la clase principal también .

Ejecutemos la prueba unitaria con los siguientes datos:

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

Aquí está el objeto deserializado:

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

Observe que, dado que no serializamos el campo de población en la clase Región , el valor de esa propiedad es nulo.

3. Externalizable vs Serializable

Repasemos las diferencias clave entre las dos interfaces:

  • Responsabilidad de serialización

La diferencia clave aquí es cómo manejamos el proceso de serialización. Cuando una clase implementa la interfaz java.io.Serializable , la JVM asume toda la responsabilidad de serializar la instancia de la clase. En el caso de Externalizable, es el programador quien debe encargarse de todo el proceso de serialización y también de deserialización.

  • Caso de uso

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.

  • 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.

  • 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.

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.

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

  • 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.

4. Conclusion

En esta breve guía de la interfaz Externalizable , analizamos las características clave, las ventajas y demostramos ejemplos de uso simple. También hicimos una comparación con la interfaz serializable .

Como de costumbre, el código fuente completo del tutorial está disponible en GitHub.