Java Clone Tutorial Part 2 - Example to Override with Mutable field

This is the second part of Java tutorial on Cloning, In first part we have seen how clone method works in Java with a simple example of cloning object, with primitives and Immutable. In this tutorial, we will take one step further and override clone method for creating clone of object with mutable field. In our case mutable field is a Collection here, to be precise a List. Since default implementation of clone() method only does shallow copy of objects, it can create issue, if original object contains mutable object or Collection classes. In our example, we have a class called Programmer, with String name, int age and List of Certifications. When we override clone() method inside Programmer class, we need to explicitly take care of this List, otherwise, both original and cloned object will point to same Collection in Java heap, which means, any change e.g. adding a new Certification in original object will also reflect in cloned object or vice-versa. Since an object should be independent of it's clone, we need to fix this issue by applying deep cloning techniques. Along with this example of overriding clone in Java, In this Java clone tutorial part 2, we will also take a look at some Java best practices for implementing correct clone method, disadvantages and shortcomings of cloning in Java and in particular clone method and finally, when to use clone in Java.




How to Override Clone method in Java

There are some guidelines mentioned about overriding clone method in Java, e.g. call should be delegated to super.clone(), by keeping that in mind, following steps should be followed while overriding clone() in Java :


1) Let the class, which supports cloning implements Cloneable interface. (failure to do this will result in CloneNotSupportedException).

2) Override protected clone() method from java.lang.Object class.

3) In overridden clone(), first call super.clone() to get the shallow copy of object.

4) If your class contains any Collection or Mutable object, than deep copy of those field. Like in our example, Programmer class contains List in it's certification field, when super.clone() will return, both original and cloned object will point to same object. To fix this, we reassign certification fields of clone object by explicitly copying data, as shown in following line :

clone.certifications = new ArrayList(certifications); //deep copying

5) Depending upon object, you may call it's clone method e.g. in case of java.util.Date or recursively copy it's data into new field. This whole process is known as deep copying. See here to know how to deep copy Java Collection.




Java Program to override Clone method with Mutable Field

Here is our sample Java program which will teach you how to implement clone method for a class which contains a mutable field. Remember, clone should be exactly same as original object but they must be different object i.e. if one reference change value of mutable member than cloned object should not be affected by that.

import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
 * Java program to show how to override clone method for deep copying.
 * This example includes a mutable filed in class to be cloned to show how you deal with
 * practical classes which contains both immutable and mutable fields.
 *
 * @author Javin
 */
public class CloneTest {

    private static final Logger logger = LoggerFactory.getLogger(Cloneclass);

    public static void main(String args[]) {
     
        Programmer javaguru = new Programmer("John", 31);
        javaguru.addCertificates("OCPJP");
        javaguru.addCertificates("OCMJD");
        javaguru.addCertificates("PMP");
        javaguru.addCertificates("CISM");
     
        logger.debug("Real Java Guru     : {}", javaguru);
     
        Programmer clone = javaguru.clone();
        logger.debug("Clone of Java Guru : {}", clone);
     
        //let's add another certification to java guru
        javaguru.addCertificates("Oracle DBA");
        logger.debug("Real Java Guru     : {}", javaguru);
        logger.debug("Clone of Java Guru : {}", clone);
    }
}

class Programmer implements Cloneable{
    private static final Logger logger = LoggerFactory.getLogger(Programmer.class);
 
    private String name;
    private int age;
    private List certifications ;

    public Programmer(String name, int age) {
        this.name = name;
        this.age = age;
        this.certifications = new ArrayList();
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
 
    public void addCertificates(String certs){
        certifications.add(certs);
    }

    @Override
    public String toString() {
        return String.format("%s, %d, Certifications: %s", name, age, certifications.toString());
    }

    @Override
    protected Programmer clone()  {
        Programmer clone = null;
        try{
            clone = (Programmer) super.clone();
            clone.certifications = new ArrayList(certifications); //deep copying
         
        }catch(CloneNotSupportedException cns){
            logger.error("Error while cloning programmer", cns);
        }
        return clone;
    }  
 
}


Output of Shallow copying :
[main] DEBUG CloneTest  - Real Java Guru     : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM]
[main] DEBUG CloneTest  - Clone of Java Guru : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM]
[main] DEBUG CloneTest  - Real Java Guru     : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM, Oracle DBA]
[main] DEBUG CloneTest  - Clone of Java Guru : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM, Oracle DBA]

After deep copying collection:
[main] DEBUG CloneTest  - Real Java Guru     : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM]
[main] DEBUG CloneTest  - Clone of Java Guru : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM]
[main] DEBUG CloneTest  - Real Java Guru     : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM, Oracle DBA]
[main] DEBUG CloneTest  - Clone of Java Guru : John, 31, Certifications: [OCPJP, OCMJD, PMP, CISM]

Remember, this code will work because String is immutable, otherwise you can not reply on copy Constructor provided by Collection classes, they only provide shallow copy and not deep copy. If you need to deep copy a collection, you need to iterate over it and clone each object separately.




Best Practices to follow while overriding clone method in Java

Now you know how to override clone method, it's time to sharper your knowledge and learn some best practices related to clone() method in Java. We will also look, what things to avoid, because best practices will not yield any result if you don't stop following bad practices.


1) Return Sub class from clone() method, instead of returning java.lang.Object

One shortcoming of clone() method is that it return Object, which means user of clone() method must do type casting to get correct type of object. From Java 1.5 onwards an overriding method can return subclass of return type declared in original method, which means you can return sub class from clone method. It is known as co-variant method overriding. This will prevent lot of type casting at client side. Unfortunately clone() method of java.util.Date is not updated to take advantage of this change made in Java 5. Which means you need to cast cloned object into Date, before using it.



2) Use deep copy, if your class contains any mutable field.

Remember to perform deep cloning before returning deep copy of object, if it contains any mutable field. Java documentation of clone method says that, you may need to modify certain fields before returning them, to make cloned object completely independent from original object. For example in java.util.Date method's clone method, we are explicitly cloning cdate field, as shown below.

d = (Date)super.clone();
if (cdate != null) {
  d.cdate = (BaseCalendar.Date) cdate.clone();
}

This step conflict with use of final field, had cdate would be final, you can not perform this step, because final fields can not be reassigned once initialized.



3) Don't throw CloneNotSupportedException.

One of the most annoying thing, while using clone() method to copy object is handling of checked exception, which is often necessary, because most of the time, client only call clone() method on object, they know supports cloning of objects. By catching CloneNotSupportedExcpetion internally in overridden clone() method, you do a lot of favor to your clients. You can take a look at java.util.Date's clone() method for this example, which doesn't throw this exception.

/*
 *  Return a copy of this object.
 */
 public Object clone() {
  Date d = null;
  try {
  d = (Date)super.clone();
         if (cdate != null) {
                d.cdate = (BaseCalendar.Date) cdate.clone();
         }
  } catch (CloneNotSupportedException e) {} // Won't happen
  return d;
 }




Shortcomings of Cloneable and clone method in Java

Overriding Clone method with mutable field in Java
Cloneable and clone() method has failed to meet expectation of creating copy of object, it's one of the most criticized design decision made by Java API designers, along with checked exception and possibly introducing NullPointerException. Apart from not providing deep copy of Object, clone() method has several other problems and because of that many Java programmers doesn't even override clone() method as often they override equals(), hashCode() and compareTo() methods.


1) Well behaved implementation of clone() method is based on convention suggested in Java documentation, rather than enforced by design. Which means it's fragile, as it makes it easy for Java programmer to forget those convention.


2) Your ability to correctly override clone() method strongly depends upon, how super class overrides it. If your super class is part of any library, which is not in your control, it decisively limit your ability to create a well behaved clone() method in Java.


3) Process of deep cloning in Java, conflicts with proper use of final fields, because if your Mutable object is a final field, you can not reassign copied values inside clone method.


4) Creating copies using clone() method is not natural, because it doesn't call constructor, which means it doesn't leverage invariant enforced by constructor of object, and demand extra care while copying, similar to deserializing an object.


5) Another annoying part of clone() method is that it throws unnecessary checked exception in form of CloneNotSupportedExcpetion, which reduces readability of code.

6) Prior to Java 1.5, every caller of clone() method must need to type cast cloned object into required type, though this can be avoided by returning correct type from overridden clone() method from java 5 onwards.



When to use Clone method in Java

Given all these problem associated with clone() method, it's risky to use it for creating copy, until you are absolutely sure that corresponding class provides well behaved implementation of clone() method. I personally prefer to use clone() method for creating copies of objects like java.util.Date, which provides correctly overridden clone() method but also doesn't throw CloneNotSupportedException. Similarly, if your class only contains primitives and Immutable object you can rely on shallow copy created by Object's clone() method. Another place, where you want to use clone() method in Java is for cloning arrays. Apart from that, I would suggest to prefer Copy Constructor and Static Factory methods for creating objects from another object. They don't come with all problems, presented by clone() method and does the job well by creating exact copies.



That's all on How to override clone() method in Java and when to use clone for creating copy of an instance. We have also seen shortcoming of clone() methods to and why it's use is discouraged by Java community, and we have also seen some Java best practices, which can be followed while overriding clone method, to minimize some annoying problem associated with clone() method in Java. As suggested by Joshua Bloch in Effective Java Item 11, prefer Copy constructor and Conversion factories over clone() method in Java.

3 comments :

Abinaya Gangaiah said...

Hi.. I tried executing the CloneTest Program. I'm getting error in this line" private static final Logger logger = LoggerFactory.getLogger(Cloneclass);". I tired "getLogger(CloneTest.class)", but it throws me the error. Please help me with this

kartik kudada said...

try system.out.println instead of logger . It may solve your problem

Javin Paul said...

@Abinaya, that's a formatting problem. Please use CloneTest.class, actually you need to pass class instance of the class you want logger, in this case its CloneTest.

Post a Comment