Wednesday, August 4, 2021

How to Clone Objects with Mutable field in Java? Example Tutorial

This is the second part of the Java tutorial on Cloning, In the first part, we have seen how the clone method works in Java with a simple example of cloning objects, with primitives and Immutable. In this tutorial, we will take one step further and override the clone method for creating a clone of objects with a mutable field. In our case the mutable field is a Collection here, to be precise a List. Since the default implementation of the clone() method only does a shallow copy of objects, it can create issues, if the original object contains mutable objects or Collection classes. In our example, we have a class called Programmer, with String name, int age, and List of Certifications.

When we override the clone() method inside the Programmer class, we need to explicitly take care of this List, otherwise, both original and cloned objects will point to the same Collection in the Java heap, which means, any change e.g. adding a new Certification in the original object will also reflect in a cloned object or vice-versa.

Since an object should be independent of its 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 the Clone method in Java? Example

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, the following steps should be followed while overriding clone() in Java :



1) Let the class, which supports cloning implements a 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 a deep copy of those fields. Like in our example, Programmer class contains List in its certification field, when super.clone() will return, both original and cloned objects will point to the same object. To fix this, we reassign certification fields of clone object by explicitly copying data, as shown in the 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 the clone method for a class that contains a mutable field. Remember, the clone should be exactly the same as an original object but they must be different object i.e. if one reference changes the value of a mutable member then 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 a 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 the 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 Subclass 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

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 the proper use of final fields because if your Mutable object is a final field, you can not reassign copied values inside the 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 the constructor of the object, and demands extra care while copying, similar to deserializing an object.


5) Another annoying part of the clone() method is that it throws an unnecessary checked exceptions in form of CloneNotSupportedExcpetion, which reduces the 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.

Overriding Clone method with mutable field in Java




When to use the Clone method in Java?

Given all these problems associated with the clone() method, it's risky to use it for creating copy, until you are absolutely sure that the corresponding class provides a well-behaved implementation of the clone() method. I personally prefer to use the 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 objects you can rely on shallow copy created by Object's clone() method. Another place, where you want to use the clone() method in Java is for cloning arrays.

Apart from that, I would suggest preferring 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 the clone() method in Java and when to use the clone for creating copy of an instance. We have also seen shortcomings of clone() methods to and why its use is discouraged by the Java community, and we have also seen some Java best practices, which can be followed while overriding clone method, to minimize some annoying problems associated with clone() method in Java. As suggested by Joshua Bloch in Effective Java Item 11, prefer the Copy constructor and Conversion factories over the clone() method in Java.

3 comments :

Unknown 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