August 27, 2022

Shallow Copy Vs Deep Copy in Java Object Cloning

In this post we’ll see the differences between shallow copy and deep copy in Java object cloning. To understand the differences it is very important to understand the concept of shallow copy and deep copy while cloning an object so first we’ll try to understand the concept using examples of both shallow copy and deep copy.

To know more about object cloning in Java please refer this post- Object Cloning in Java Using clone() Method

Shallow copy in Java object cloning

When an object is cloned using clone() method a bit wise copy happens where each field’s value in the original object is copied to the cloned object’s corresponding field. This way of object cloning in Java using the default clone() method creates a Shallow copy.

This is known as shallow copy because this process of creating an exact copy works fine for primitive values but has a shared state between the original object and cloned object when original object holds reference to another object.

In that case that reference is copied as it is in the cloned object and both object share that object reference.

For example if there is a class MyClass which has another object objA as a field.

class MyClass implements Cloneable{
  private ClassA objA;
  ...
  ...
}

If there is an object myObj of class MyClass and that is cloned then the objA reference is shared between myObj and cloned copy of myObj.

shallow copy in Java

Shallow copy in Java example

In the example there are two classes One and Two. In Class Two there is a reference to an object of class One.

class One implements Cloneable{
  int i;
  String str;
  One(int i, String str){
    this.i = i;
    this.str = str;
  }
  // overriding clone method
  public Object clone() throws CloneNotSupportedException{
    return super.clone();
  }
  public int getI() {
    return i;
  }
  public void setI(int i) {
    this.i = i;
  }
  public String getStr() {
    return str;
  }
  public void setStr(String str) {
    this.str = str;
  }
}

class Two implements Cloneable{
  int j;
  // Object reference
  One obj;
  Two(int j, One obj){
    this.j = j;
    this.obj = obj;
  }
  public Object clone() throws CloneNotSupportedException{
    Two objCloned =  (Two) super.clone();
    return objCloned;
  }
  public int getJ() {
    return j;
  }
  public void setJ(int j) {
    this.j = j;
  }
  public One getObj() {
    return obj;
  }
  public void setObj(One obj) {
    this.obj = obj;
  }
}

public class CloningDemo {
  public static void main(String[] args) {
    One one = new One(10, "Clone Test");
    Two two = new Two(5, one);
    try {
      Two objCopy = (Two) two.clone();
      System.out.println("Original object- " +  two.getJ() + " " + two.getObj().str);
      System.out.println("Cloned object- " +  + objCopy.getJ() + " " + objCopy.getObj().str);
      // modifying field in the referenced object
      objCopy.getObj().setStr("New Value");
      // Modifying primtive value
      objCopy.setJ(25);
      System.out.println("---After changing value---");
      System.out.println("Original object- " +  two.getJ() + " " + two.getObj().str);
      System.out.println("Cloned object- " +  + objCopy.getJ() + " " + objCopy.getObj().str);
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 	
  }
}
Output
Original object- 5 Clone Test
Cloned object- 5 Clone Test
---After changing value---
Original object- 5 New Value
Cloned object- 25 New Value

As you can see from the output when the field in the referenced object is changed in the copy that change is reflected in the original object too. Change in primitive type field done in the copy (value of j changed to 25) is not reflected in the original object.

Deep copy in Java object cloning

To avoid such sharing of state between the original object and cloned object where the mutation of the referenced object is reflected in both the objects deep copy is required. In case of deep copy while cloning an object in Java even the referenced objects are created separately so that both objects have their own independent references.

For example if there is a class MyClass which has another object objA as a field.

class MyClass implements Cloneable{
  private ClassA objA;
  ...
  ...
}

If there is an object myObj of class MyClass and and a deep copy is created then even objA’s separate and independent copy is there for myObj and cloned copy of myObj.

deep copy in Java

Deep copy in Java example

When you do a deep copy there are two scenarios, both of which are discussed here with examples-

1- If class whose object is referenced implements Cloneable interface then you can explicitly call clone() method of that object too.

In the example you can see that the clone method is overridden in class Two and clone() method for the referenced object is also called explicitly to create a distinct copy of that object too.

class One implements Cloneable{
  int i;
  String str;
  One(int i, String str){
    this.i = i;
    this.str = str;
  }
  // overriding clone method
  public Object clone() throws CloneNotSupportedException{
    return super.clone();
  }
  public int getI() {
    return i;
  }
  public void setI(int i) {
    this.i = i;
  }
  public String getStr() {
    return str;
  }
  public void setStr(String str) {
    this.str = str;
  }
}

class Two implements Cloneable{
  int j;
  // Object reference
  One obj;
  Two(int j, One obj){
    this.j = j;
    this.obj = obj;
  }
  public Object clone() throws CloneNotSupportedException{
    Two objCloned =  (Two) super.clone();
    // Explicitly calling clone method for
    // object of Class One
    objCloned.obj = (One) obj.clone();
    return objCloned;
  }
  public int getJ() {
    return j;
  }
  public void setJ(int j) {
    this.j = j;
  }
  public One getObj() {
    return obj;
  }
  public void setObj(One obj) {
    this.obj = obj;
  }
}

public class CloningDemo {

  public static void main(String[] args) {
    One one = new One(10, "Clone Test");
    Two two = new Two(5, one);
    try {
      Two objCopy = (Two) two.clone();
      System.out.println("Original object- " +  two.getJ() + " " + two.getObj().str);
      System.out.println("Cloned object- " +  + objCopy.getJ() + " " + objCopy.getObj().str);
      // modifying field in the referenced object
      objCopy.getObj().setStr("New Value");
      // Modifying primtive value
      objCopy.setJ(25);
      System.out.println("---After changing value---");
      System.out.println("Original object- " +  two.getJ() + " " + two.getObj().str);
      System.out.println("Cloned object- " +  + objCopy.getJ() + " " + objCopy.getObj().str);
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 	
  }
}
Output
Original object- 5 Clone Test
Cloned object- 5 Clone Test
---After changing value---
Original object- 5 Clone Test
Cloned object- 25 New Value

As you can see when the field in the referenced object is changed in the copy that change is not reflected in the original object when deep copy is done.

2- If a class whose object is referenced doesn’t implement Cloneable interface then CloneNotSupportedException is thrown if clone() method is called upon it. In this case you need to create a new object and assign values for the fields explicitly in the clone() method.

class One{
  int i;
  String str;
  One(int i, String str){
    this.i = i;
    this.str = str;
  }
  // overriding clone method
  public Object clone() throws CloneNotSupportedException{
    return super.clone();
  }
  public int getI() {
    return i;
  }
  public void setI(int i) {
    this.i = i;
  }
  public String getStr() {
    return str;
  }
  public void setStr(String str) {
    this.str = str;
  }
}

class Two implements Cloneable{
  int j;
  // Object reference
  One obj;
  Two(int j, One obj){
    this.j = j;
    this.obj = obj;
  }
  public Object clone() throws CloneNotSupportedException{
    Two objCloned =  (Two) super.clone();
    // Creating new object
    One newObj = new One(12, "New Value");
    // assigning new oject to the clone
    objCloned.setObj(newObj);
    return objCloned;
  }
  public int getJ() {
    return j;
  }
  public void setJ(int j) {
    this.j = j;
  }
  public One getObj() {
    return obj;
  }
  public void setObj(One obj) {
    this.obj = obj;
  }
}

public class CloningDemo {

  public static void main(String[] args) {
    One one = new One(10, "Clone Test");
    Two two = new Two(5, one);
    try {
      Two objCopy = (Two) two.clone();
      System.out.println("Original object- " +  two.getJ() + " " + two.getObj().getI() + " " + two.getObj().getStr());
      System.out.println("Cloned object- " +  + objCopy.getJ() + " "  + objCopy.getObj().getI() + " " + objCopy.getObj().getStr());
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 	
  }
}
Output
Original object- 5 10 Clone Test
Cloned object- 5 12 New Value

Shallow copy Vs Deep Copy in Java

  1. Shallow copies are inexpensive and easy to implement. Deep copies are expensive as each referenced object has to be created separately. It is also more complex as object tree may be very long.
  2. In case of shallow copy though a distinct copy of an object is created with its own set of fields but object references are shared. In case of deep copy even for the referenced objects separate copies are created.
  3. By default Object class’ clone method creates a shallow copy. For creating a deep copy you need to override clone method and call the clone method on the referenced objects too.

That's all for the topic Shallow Copy Vs Deep Copy in Java Object Cloning. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment