Tuesday, 18 June 2019

Serialization | Java Serialization with Aggregation (HAS-A Relationship)

If a class has a reference to another class, all the references must be Serializable otherwise serialization process will not be performed. In such case, NotSerializableException is thrown at runtime.

Address.java
public class Address {

    String hNo, city;

    public Address(String hNo, String city) {
        this.hNo = hNo;
        this.city = city;
    }

    @Override
    public String toString() {
        return String.format("HNo. %s, City: %s", hNo, city);
    }
}

Employee.java
import java.io.Serializable;
public class Employee implements Serializable, ObjectInputValidation {

    private static final long serialVersionUID = -7711664869917950975L;

    private int id;
    private String name;
    private Address address;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee addAddress(Address address) {
        this.address = address;
        return this;
    }

    @Override
    public String toString() {
        return String.format("[ Id: %s, Name: %s, Address: %s ]", id, name, address);
    }
}

TestSerialization.java
import java.io.*;

public class TestSerialization {
    private static Employee createEmpObject() {
        return new Employee(1, "Awadh").addAddress(new Address("2209/1", "Mathura"));
    }

    private static Employee deserializeObjectUtil(String fileName) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
            return (Employee) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void seriliazeObjectUtil(String fileName, Employee message) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        String fileName = "employee.ser";

        // Create Employee object.
        Employee emp1 = createEmpObject();
        System.out.println("Employee object before serialization "+ emp1);

        // serialize and store the Employee object instance in file.
        seriliazeObjectUtil(fileName, emp1);

        // Deserialize Employee object instance from the file.
        Employee emp2 = deserializeObjectUtil(fileName);

        System.out.println(emp2);
    }
}

Since Address is not Serializable, we are getting NotSerializableException while serializing the instance of Employee class.

How to fix the Serialization in the case of Association?
1. Using the transient keyword
In case the class refers to non-serializable objects and these objects should not be serialized, then, you can declare these objects as transient. Once a field of a class is declared as transient, then, it is ignored by the serializable runtime.

2. Using the static keyword
In serialization, static variables are not serialized, so during deserialization, static variable value will load the class.

3. Make it a Serializable object
All the objects within an object must be Serializable. To resolve the problem in the above example, we can implement the Serializable in Address class.

4. Modify the Serialization by implementing the readObject and writeObject methods

import java.io.*;
public class Employee implements Serializable, ObjectInputValidation {

    private static final long serialVersionUID = -7711664869917950975L;

    private int id;
    private String name;
    private Address address;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee addAddress(Address address) {
        this.address = address;
        return this;
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        System.out.println("Inside writeObject method");
        out.defaultWriteObject();
        out.writeObject(this.id + "@" + this.name);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        System.out.println("Inside readObject method");
        in.registerValidation(this, 0);
        in.defaultReadObject();
        String[] str = ((String) in.readObject()).split("@");
        this.id = Integer.valueOf(str[0]);
        this.name = str[1];
    }

    @Override
    public String toString() {
        return String.format("[ Id: %s, Name: %s, Address: %s ]", id, name, address);
    }

    @Override
    public void validateObject() throws InvalidObjectException {

    }
}

Phew! It is not working and throwing the same exception. writeObject and readObject methods have control only on the serialization of the class in which those are implemented.

5. Make it an Externalizable object
We can resolve this problem by using externalization which has complete command on the class.

import java.io.*;

public class Employee implements Externalizable {

    private static final long serialVersionUID = -7711664869917950975L;

    private int id;
    private String name;
    private Address address;

    // Must have no-args constructor otherwise, java.io.InvalidClassException: no valid constructor
    public Employee() {

    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee addAddress(Address address) {
        this.address = address;
        return this;
    }

    @Override
    public String toString() {
        return String.format("[ Id: %s, Name: %s, Address: %s ]", id, name, address);
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(id);
        out.writeObject(name);
        out.writeObject(address.hNo + "@" + address.city);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        id = in.readInt();
        name = (String) in.readObject();
        String[] addStr = ((String) in.readObject()).split("@");
        address = new Address(addStr[0], addStr[1]);
    }
}

After running the code, we can see that Address object is Serialized and Deserialized along with the Employee object. This is the best use case which helps us to understand the advantage of Externalizable over Serializable. Externalizable having an advantage when we want to serialize the third party API’ classes used in the association.



No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...