Skip to main content

Java Cloning and Types of Cloning (Shallow and Deep) in Details with Example

In my previous article 5 Different ways to create objects in Java with Example, I have discussed 5 different ways (new keyword, Class.newInstance() method, Constructor.newInstance() method, clone() method and deserialization) a developer can use to create objects, if you haven't read it please go ahead.

Cloning is also a way of creating an object but in general, cloning is not just about creating a new object. Cloning means creating a new object from an already present object and copying all data of given object to that new object.

In order to create a clone of an object we generally design our class in such way that
  1. Our class should implement Cloneable interface otherwise, JVM will throw CloneNotSupportedException if we will call clone() on our object.
    Cloneable interface is a marker interface which JVM uses to analyse whether this object is allowed for cloning or not. According to JVM if yourObject instanceof Cloneable then create a copy of the object otherwise throw CloneNotSupportedException.
  2. Our class should have clone method which should handle CloneNotSupportedException.
    It is not necessary to define our method by the name of clone, we can give it any name we want e.g. createCopy(). Object.clone() method is protected by its definition so practically child classes of Object outside the package of Object class (java.lang) can only access it through inheritance and within itself. So in order to access clone method on objects of our class we will need to define a clone method inside our class and then class Object.clone() from it. For details on protected access specifier, please read Why an outer Java class can’t be private or protected .
  3. And finally, we need to call the clone() method of the superclass, which will call its super’s clone() and this chain will continue until it will reach to clone() method of the Object class. Object.clone() method is the actual worker who creates the clone of your object and another clone() methods just delegates the call to its parent’s clone().
All the things also become disadvantage of the cloning strategy to copy the object because all of above steps are necessary to make cloning work. But we can choose to not do all above things follow other strategies e.g. Copy Constructors, To know more read Java Cloning - Copy Constructor versus Cloning.
Java Cloning (Shallow Cloning, Deep Cloning)Example

To demonstrate cloning we will create two classes Person and City and override
  1. toString() to show the content of the person object,
  2. equals() and hashCode() method to compare the objects,
  3. clone() to clone the object
However, we are overriding the clone method but it not necessary we can create clone method by any name but if we are naming it as clone then we will need to follow basic rules of method overriding e.g. method should have same name, arguments and return type (after Java 5 you can also use a covariant type as return type). To know more about method overriding read Everything About Method Overloading Vs Method Overriding.

class Person implements Cloneable {
private String name; // Will holds address of the String object, instead of object itself
private int income; // Will hold bit representation of int, which is assigned to it
private City city; // Will holds address of the City object, instead of City object

public String getName() {
return name;
}
public void setName(String firstName) {
this.name = firstName;
}
public int getIncome() {
return income;
}
public void setIncome(int income) {
this.income = income;
}
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}

public Person(String firstName, int income, City city) {
super();
this.name = firstName;
this.income = income;
this.city = city;
}

// But we can also create using any other name
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}

// To print the person object
@Override
public String toString() {
return "Person [name=" + name + ", income=" + income + ", city=" + city + "]";
}

// hasCode(), and equals() to compare person objects
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + income;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (income != other.income)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

Person class has a reference to City class which looks like below

class City implements Cloneable {
private String name;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

public City(String name) {
super();
this.name = name;
}

@Override
public City clone() throws CloneNotSupportedException {
return (City) super.clone();
}

@Override
public String toString() {
return "City [name=" + name + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
City other = (City) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

And let's test it

public class CloningExample {

public static void main(String[] args) throws CloneNotSupportedException {

City city = new City("Dehradun");
Person person1 = new Person("Naresh", 10000, city);
System.out.println(person1);

Person person2 = person1.clone();
System.out.println(person2);

if (person1 == person2) { // Evaluate false, because person1 and person2 holds different objects
System.out.println("Both person1 and person2 holds same object");
}

if (person1.equals(person2)) { // Evaluate true, person1 and person2 are equal and have same content
System.out.println("But both person1 and person2 are equal and have same content");
}

if (person1.getCity() == person2.getCity()) {
System.out.println("Both person1 and person2 have same city object");
}
}
}

person1.clone() calls super.clone() which means Object.clone() method.
Object.clone() method copy content of the object to other object bit-by-bit, means the values of all instance variables from one object will get copied to instance variables of other object.


So (person1 == person2) will evaluate false because person1 and person2 are the copy of each other but both are different objects and holds different heap memory. While person1.equals(person2) evaluate true because both have the same content.

But as we know reference variables holds address of the object instead of object itself, which can also be referred from other reference variables and if we change one other will reflect that change.

So while cloning process Object.clone() will copy address which person1.city is holding to person2.city, So now city, person1.city, and person2.city all are holding same city object. That’s why (person1.getCity() == person2.getCity()) evaluate true. This behavior of cloning is known as Shallow Cloning.

Types of Cloning

This behavior of Object.clone() method classifies cloning into two sections

1. Shallow Cloning

Default cloning strategy provided by Object.clone() which we have seen. The clone() method of object class creates a new instance and copy all fields of the Cloneable object to that new instance (either it is primitive or reference). So in the case of reference types only reference bits gets copied to the new instance, therefore, the reference variable of both objects will point to the same object. The example we have seen above is an example of Shallow Cloning.

2. Deep Cloning

As the name suggest deep cloning means cloning everything from one object to another object. To achieve this we will need to trick our clone() method provide our own cloning strategy. We can do it by implementing Cloneable interface and override clone() method in every reference type we have in our object hierarchy and then call super.clone() and these clone() methods in our object’s clone method.

So we can change clone method of Person class in below way

public Person clone() throws CloneNotSupportedException {
Person clonedObj = (Person) super.clone();
clonedObj.city = this.city.clone();
return clonedObj;
}

Now (person1.getCity() == person2.getCity()) will evaluate false because in clone() method of Person class we are clonning city object and assigning it to the new clonned person object.

In below example we have deep copied city object by implementing clone() in City class and calling that clone() method of person class, That's why person1.getCity() == person2.getCity() evaluate false because both are separate objects. But we have not done same with Country class and person1.getCountry() == person2.getCountry() evaluate true.

public class CloningExample {

public static void main(String[] args) throws CloneNotSupportedException {

City city = new City("Dehradun");
Country country = new Country("India");
Person person1 = new Person("Naresh", 10000, city, country);
System.out.println(person1);

Person person2 = person1.clone();
System.out.println(person2);

// Evaluate false, because person1 and person2 holds different objects
if (person1 == person2) {
System.out.println("Both person1 and person2 holds same object");
}

// Evaluate true, person1 and person2 are equal and have same content
if (person1.equals(person2)) {
System.out.println("But both person1 and person2 are equal and have same content");
}

// Evaluate false
if (person1.getCity() == person2.getCity()) {
System.out.println("Both person1 and person2 have same city object");
}

// Evaluate true, because we have not implemented clone in Country class
if (person1.getCountry() == person2.getCountry()) {
System.out.println("Both person1 and person2 have same country object");
}

// Now lets change city and country object and print person1 and person2
city.setName("Pune");
country.setName("IN");

// person1 will print new Pune city
System.out.println(person1);
// while person2 will still print Dehradun city because person2.city holds a separate city object
System.out.println(person2);
}
}

class Person implements Cloneable {
private String name; // Will holds address of the String object which lives
// in SCP, instead of String object itself
private int income; // Will hold bit representation of int, which is assigned to it
private City city; // Will holds address of the City object which lives in
// heap, instead of City object
private Country country;

public String getName() {
return name;
}

public void setName(String firstName) {
this.name = firstName;
}

public int getIncome() {
return income;
}

public void setIncome(int income) {
this.income = income;
}

public City getCity() {
return city;
}

public void setCity(City city) {
this.city = city;
}

public Country getCountry() {
return country;
}

public void setCountry(Country country) {
this.country = country;
}

public Person(String name, int income, City city, Country country) {
super();
this.name = name;
this.income = income;
this.city = city;
this.country = country;
}

@Override
public Person clone() throws CloneNotSupportedException {
Person clonedObj = (Person) super.clone();
clonedObj.city = this.city.clone();
return clonedObj;
}

@Override
public String toString() {
return "Person [name=" + name + ", income=" + income + ", city=" + city + ", country=" + country + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + ((country == null) ? 0 : country.hashCode());
result = prime * result + income;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (country == null) {
if (other.country != null)
return false;
} else if (!country.equals(other.country))
return false;
if (income != other.income)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

class City implements Cloneable {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public City(String name) {
super();
this.name = name;
}

public City clone() throws CloneNotSupportedException {
return (City) super.clone();
}

@Override
public String toString() {
return "City [name=" + name + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
City other = (City) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

class Country {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Country(String name) {
super();
this.name = name;
}

@Override
public String toString() {
return "Country [name=" + name + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Country other = (Country) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

Java cloning is not considered a good way to copy an object and lots of other ways are there to do the same. You can read Java Cloning - Copy Constructor versus Cloning to get more knowledge on why Java cloning is not a preferred way of cloning and what are other ways to overcome this.

Comments

  1. If we want to clone String object by using deep cloning then you need to create another string object by passing existing string object.

    // No @Override, means we are not overriding clone
    public Person clone() throws CloneNotSupportedException {
    Person clonedObj = (Person) super.clone();
    clonedObj.city = this.city.clone();
    clonedObj.name = new String(this.name);
    return clonedObj;
    }

    ReplyDelete
  2. HAving an @Override annotation is not must, you aren't overriding because the original method returns object, so you cannot put the Override annotation. From you code it implies that because we took the annotation off that's the reason why we aren't overriding.

    ReplyDelete
    Replies
    1. Yes, it is really confusing statement however what I want to say is because we are not overriding that's why we are not putting @Override on it.

      Delete
  3. I think we are overriding.. as of JSE 5 covariant returns are possible so theasy clone method is overriding the super class method in this case.

    ReplyDelete

Post a Comment

Popular posts from this blog

Why an outer Java class can’t be static

In a previous blog , I talked about why we can not define an outer class using private or protected keywords. If you have not read it, please go ahead and give it a look. I this article I will talk what is the use of the static keyword, why an outer Java class can’t be static, why it is not allowed in Java to define a static outer class. In order to understand that first, we need to understand what is the static keyword used for, what purpose it solves and how does it works. What does static keyword do Every Java programmer knows that if we need to define some behavior (method) or state (field) which will be common to all objects we define it as static. Because static content (behavior or state) does not belong to any particular instance or object, it will common to all objects and all objects are free to change any static field and every change will be visible to every object. We do not need to create any object of the class to access a static field or method, we can directly...

Why Single Java Source File Can Not Have More Than One public class

According to Java standards and common practices we should declare every class in its own source file. And even if we declare multiple classes in the single source file (.java) still each class will have its own class file after compilation. But the fact is that we can declare more than one class in a single source file with below constraints, Each source file should contain only one public class and the name of that public class should be similar to the name of the source file. If you are declaring the main method in your source file then main should lie in that public class If there is no public class in the source file then main method can lie in any class and we can give any name to the source file. If you are not following 1st constraint then you will receive a compilation error saying “ The public type A must be defined in its own file ”.  While if you are not following the second constraint you will receive an error “ Error: Could not find or load main class User ” after ...

How Does JVM Handle Method Overloading and Overriding Internally

In my previous article Everything About Method Overloading Vs Method Overriding , I have discussed method overloading and overriding, their rules and differences. In this article, we will see How Does JVM Handle Method Overloading And Overriding Internally, how JVM identifies which method should get called. Let’s take the example of parent class  Mammal and a child  Human classes from our previous blog to understand it more clearly. public class OverridingInternalExample { private static class Mammal { public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); } } private static class Human extends Mammal { @Override public void speak() { System.out.println("Hello"); } // Valid overload of speak public void speak(String language) { if (language.equals("Hindi")) System.out.println("Namaste"); else System.out.println("Hello"); } @...