In this tutorial, We’ll discuss about java’s java.io.Externalizable interface. The main goal of this interface is to provide custom serialization and deserialization.
Before we go ahead, make sure you check out the Java Serialization article.
Externalizable Interface
The java.io.Externalizable Interface is the child of java.io.Serializable interface. Externalizable interface offers customized serialization.
The Externalizable interface declares following two methods:
- public void writeExternal(ObjectOutput out) throws IOException : For custom serialization
- public void readExternal(ObjectInput in) throws IOException : For custom de-serialization
Any class that implements Externalizable interface should override the writeExternal(), readExternal() methods. Here there is no JVM’s default serialization behavior. We should mention which attributes to be serialized and de-serialized. Where as in the case of Serialization Interface, all the attributes are serialized by default.
Here we don’t need any special care of transient, static and final variables, Because here serialization and de-serialization is in our control, we can serialize and de-serialize what ever the attribute we want.
Externalization Interface needs Public No-Arg Constructor to reconstruct the object in the de-serialization process.
Even we can manually call writeExternal(), readExternal() methods to do serialization and de-serialization process.
Serialization and Deserialization with Externalizable
First we need to define a domain class which implements Externalizable interface with it’s attributes. Here in our example we are defining Employee class with serialVersionUID, and instance variable, final variable, static variable, transient variable, no-arg constructor and arg constructor, toString(), writeExternal(), readExternal() methods as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
package com.vidvaan.corejava.externalizable01.serialization; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; //Here there is no JVM's default serialization behavior. // Here we should do custom serialization and deserialization public class Employee implements Externalizable { // This serialVersionUID field is to verify that the saved and loaded objects // have the same attributes private static final long serialVersionUID = 2L; // As we are doing custom serialization, even instance variables should be // serialized explicitly private int employeeId; // As we are doing custom serialization, even we can serialize static variables private static String name; // As we are doing custom serialization, even we can serialize transient // variables private transient double salary; // As we are doing custom serialization, even we can serialize final variables private final String department; // default constructor is used to reconstruct the object in the de-serialization public Employee() { System.out.println("------------Employee No-Arg Constructor------------"); department = "Default Department";// final variables should be assinged in constructors } public Employee(int employeeId, String name, double salary, String department) { System.out.println("------------Employee Arg Constructor------------"); this.employeeId = employeeId; this.name = name; this.salary = salary; this.department = department; } // custom serialization public void writeExternal(ObjectOutput out) throws IOException { System.out.println("------------writeExternal(ObjectOutput out)------------"); out.writeInt(this.employeeId); out.writeObject(this.name); out.writeDouble(this.salary); out.writeObject(this.department); } // custom de-serialization public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.employeeId = in.readInt(); this.name = (String) in.readObject(); this.salary = in.readDouble(); // Even final variables are de-serialized we can't assign that value to final // variable // But in the case of Serializable interface, JVM can assign value to final // instance variable in the de-serialization process String dept = (String) in.readObject(); System.out.println("In deserializtion - department : " + dept); // this.department = (String) in.readObject(); // Compilation Error: The final field Employee.department cannot be assigned } @Override public String toString() { return "Employee [employeeId=" + employeeId + ", name=" + name + ", salary=" + salary + ", department=" + department + "]"; } } |
In the above example, comments explain each variable and their behavior clearly.
For serializing, In the writeExternal() method, we’re adding the object’s properties to the ObjectOutput stream. This has standard methods like writeObject() for String and writeInt() for the int and writeDouble() for double values.
Next, For de-serializing, In the readExternal() method, we’re reading from the ObjectInput stream using the readObject(), readInt(), readDouble() methods to read the properties in the same exact order in which they were written.
It’s a good practice to add the serialVersionUID manually. If this is absent, the JVM will automatically add one.
To serialize and de-serialize objects, We are going to use same Java API given ObjectInputStream and ObjectOutputStream which we used in the case of Serializable interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.vidvaan.corejava.externalizable01.serialization; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializationAndDeserializationExample { public static void main(String[] args) throws IOException, ClassNotFoundException { Employee empObj = new Employee(1001, "Sekhar Reddy", 99999, "IT"); System.out.println("Before serialization => " + empObj.toString()); // Serialization serialize(empObj); // Deserialization Employee deserialisedEmpObj = deserialize(); System.out.println("After deserialization => " + deserialisedEmpObj.toString()); } // Serialization code private static void serialize(Employee empObj) throws IOException { try (FileOutputStream fos = new FileOutputStream("Employee.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(empObj); } } // Deserialization code private static Employee deserialize() throws IOException, ClassNotFoundException { try (FileInputStream fis = new FileInputStream("Employee.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { return (Employee) ois.readObject(); } } } |
Output : When we run the above program, it will create a file “Employee.ser” in the project directory. The same will be loaded in the de-serialization process.
————Employee Arg Constructor————
Before serialization => Employee [employeeId=1001, name=Sekhar Reddy, salary=99999.0, department=IT]
————writeExternal(ObjectOutput out)————
————Employee No-Arg Constructor————
In deserializtion – department : IT
After deserialization => Employee [employeeId=1001, name=Sekhar Reddy, salary=99999.0, department=Default Department]
As per the above output,
- We can see that all instance, transient, static and final variables can be successfully serialized and de-serialized.
- The public No-Arg constructor has invoked in the de-serialization process
- The final variable department cannot be assigned after de-serialization.
Java Externalizable with Inheritance(is-a Relationship)
If parent class is Externalizable class, then by default child class is also Externalizable. Here we should override writeExternal(), readExternal() methods in both parent and child class. Every subclass need to call their parent writeExternal(), readExternal() methods before they serialize their own attributes.
So we should implement the writeExternal(), readExternal() methods for every sub-class of the inheritance hierarchy.
Let’s look into the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
package com.vidvaan.corejava.externalizable02.inheritance; import java.io.Externalizable; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; // Since Parent is Serializable then both parentVariables and childVariables gets serialized and de-serialized class Parent implements Externalizable { private static final long serialVersionUID = 1L; private String parentVariable; public Parent() { System.out.println("-------------Parent No-Arg Constructor-------------"); } public Parent(String parentVariable) { System.out.println("-------------Parent Arg Constructor-------------"); this.parentVariable = parentVariable; } public String getParentVariable() { return parentVariable; } public void setParentVariable(String parentVariable) { this.parentVariable = parentVariable; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(parentVariable); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.parentVariable = (String) in.readObject(); } } // By inheritance as parent Serializable, Then Child too Serializable class Child extends Parent { private static final long serialVersionUID = 1L; private String childVariable; // default constructor is used to reconstruct the object in the de-serialization public Child() { System.out.println("-------------Child No-Arg Constructor-------------"); } public Child(String childVariable, String parentVariable) { super(parentVariable); System.out.println("-------------Child Arg Constructor-------------"); this.childVariable = childVariable; } public String getChildVariable() { return childVariable; } public void setChildVariable(String childVariable) { this.childVariable = childVariable; } @Override public void writeExternal(ObjectOutput out) throws IOException { // Here we are calling parent writeExternal() method to make sure parent class // variables serialized super.writeExternal(out); out.writeObject(childVariable); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // Here we are calling parent readExternal() method to make sure parent class // variables de-serialized super.readExternal(in); this.childVariable = (String) in.readObject(); } } public class ParentClassExternalizableDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { Child child = new Child("Vidvaan-Child", "Vidvaan-Parent"); System.out.println("-------------Before serialization-------------"); System.out.println("parentVariable = " + child.getParentVariable()); System.out.println("childVariable = " + child.getChildVariable()); // Serialization serialize(child); // Deserialization Child obj = deserialize(); System.out.println("-------------After deserialization-------------"); System.out.println("parentVariable = " + obj.getParentVariable()); System.out.println("childVariable = " + obj.getChildVariable()); } // Serialization code private static void serialize(Child obj) throws IOException { try (FileOutputStream fos = new FileOutputStream("Child.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(obj); } } // Deserialization code private static Child deserialize() throws IOException, ClassNotFoundException { try (FileInputStream fis = new FileInputStream("Child.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { return (Child) ois.readObject(); } } } |
Output :
————-Parent Arg Constructor————-
————-Child Arg Constructor————-
————-Before serialization————-
parentVariable = Vidvaan-Parent
childVariable = Vidvaan-Child
————-Parent No-Arg Constructor————-
————-Child No-Arg Constructor————-
————-After deserialization————-
parentVariable = Vidvaan-Parent
childVariable = Vidvaan-Child
Note that we called super.writeExternal(out), super.readExternal(in) within child class methods to save/restore the parent class fields as well.
Note that while de-serialization Parent and Child no-arg constructors are invoked.
Externalizable vs Serializable
Let’s go through the key differences between the two interfaces:
Factor | Serializable | Externalizable |
---|---|---|
Serialization Responsibility | JVM takes the responsibility of serialization and de-serialization | Developer takes the responsibility of serialization and de-serialization |
Methods | marker interface | Contains two methods: writeExternal() and readExternal() |
public No-Arg Constructor | Not Required(Uses reflection to reconstruct object in de-serialization process) | Required (Uses no-arg constructor to reconstruct object in de-serialization process) |
Performance | Slow | Fast |
Use Case | Best option for serialize the entire object | Best option for customized serialization |