Monday, January 26, 2015

Cloning an Object - Deep and Shallow Copy

In Java, Cloning is a way to create an identical object adhering to some properties. clone() is one of the methods provided by the Object class. When we look at the javadoc of the clone method, it is explained as (The definition is short and modified version).

A Clone object should follow the properties even though these are not forced. ( a is an object of any type )
  • a != a.clone() must be true. 
  • a.getClass() should be equal to a.clone().getClass()
  • a.equals(a.clone()) must be true
In addition to the above properties, to make clone method to work, the class of the object must implement Cloneable interface otherwise, a cached exception CloneNotSupportedException will be thrown. (Even thought the Cloneable interface doesn't hold the method clone() but it should be implemented to clone an object).

Shallow and Deep Copying

Java default implementation of clone method clones only primitive members and copies the references of the other class type variables. This is Shallow Coping. Just call super.clone() inside the clone method. 

Example of Shallow Copy

Rectangle.java
public class Rectangle implements Cloneable
{
    private Long length;
    private Long breadth;
    
    public Rectangle(Long l, Long b)
    {
        this.length = l;
        this.breadth = b;
    }
    //getters and setters are ignored.

    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
    @Override
    public boolean equals(Object obj)
    {
        if(obj instanceof Rectangle)
        {
            Rectangle other = (Rectangle)obj;
            return (other.length == length && other.breadth == breadth);
        }
        return false;
    }
    @Override
    public int hashCode()
    {
        int hashCode = 0;
        if(length != null)
            hashCode += length.hashCode();
        if(breadth != null)
            hashCode += breadth.hashCode();
        return hashCode;
    }
    @Override
    public String toString()
    {
        StringBuffer buffer = new StringBuffer();
        buffer.append("Length : ");
        buffer.append(length);
        buffer.append("; Breadth : ");
        buffer.append(breadth);
        return buffer.toString();
    }
}

Sample Execution
        Rectangle r = new Rectangle(10L, 12L);
        try
        {
            Rectangle s = (Rectangle)r.clone();
            System.out.println("r is : "+r);
            System.out.println("s is : "+s);
            System.out.println("Properties");
            System.out.println("r == s : "+(r == s));
            System.out.println("r.equals(s) : "+(r.equals(s)));
            System.out.println("r.getClass() == s.getClass() : "+(r.getClass() == s.getClass()));
        } catch (CloneNotSupportedException e)
        {
            e.printStackTrace();
        }

Output looks like:
 
r is : Length : 10; Breadth : 12
s is : Length : 10; Breadth : 12
Properties
r == s : false
r.equals(s) : true
r.getClass() == s.getClass() : true

Points to be noted

  • No need to write any implementation as Java by default does the shallow copying. 
  • clone method always return object of type Object
  • clone method throws CloneNotSupportedException
  • call super.clone() when only shallow copying is required Otherwise we need to copy the remaining objects.
  • Rectangle method implemented Cloneable Interface otherwise clone() cannot be called on the object of type Rectangle. 
  • hashCode and equals methods also to be implemented otherwise equals() doesn't return true when object and it's clone are compared.

Deep Copying

If the class contains non-primitive type members, the default implementation copies the references instead of creating a copy. So, the cloned object won't be a real copy. In order to clone the object with non-primitive members, we should explicitly copy the members. 

Example of Deep Copy

Person.java
public class Person implements Cloneable
{
    private String name;
    private Address address;

    //Getters and setters ignored.

    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        Person p = (Person) super.clone();
        p.setAddress((Address)getAddress().clone());
        return p;
    }
    @Override
    public int hashCode()
    {
        int hashCode = 0;
        if(name != null)
            hashCode += name.hashCode();
        if(address != null)
            hashCode += address.hashCode();
        return hashCode;
    }
    @Override
    public boolean equals(Object obj)
    {
        if(obj instanceof Person)
        {
            Person other = (Person)obj;
            return (other.getName().equals(name) && other.getAddress().equals(getAddress()));
        }
        return false;
    }
    
}

Address.java
public class Address implements Cloneable
{
    private String city;
    private String country;
    //Getters and setters are ignored.

    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
    @Override
    public int hashCode()
    {
        int hashCode = 0;
        if(city != null)
            hashCode += city.hashCode();
        if(country != null)
            hashCode += country.hashCode();
        return hashCode;
    }
    @Override
    public boolean equals(Object obj)
    {
        if(obj instanceof Address)
        {
            Address other = (Address)obj;
            return (other.getCity().equals(city) && other.getCountry().equalsIgnoreCase(country));        
        }
        return false;
    }
}

Points to be noted

  • Person class contains an member of type Address. If clone is not called on address type, then only name of the person will be copied onto the cloned object.
  • Instead of using the clone method on the Address object, we can copy field by field. (little clumsy though, if we wish we can).
  • The three properties still hold on all the objects which are Cloneable (Person and Address both).
  • To make clone to work, either all the sub-classes need to implement Cloneable or write the logic to copy the members.
And finally, the three properties which are followed by above classes need not to be satisfied, or Java doesn't force to implement but it's always good practice to make the class to follow if it has to be cloned. Otherwise write a simple method copy to create a new copied object, instead of using Clone method. 

Happy Learning!!!!