DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case
  • Singleton: 6 Ways To Write and Use in Java Programming

Trending

  • Integrating Security as Code: A Necessity for DevSecOps
  • Unlocking AI Coding Assistants Part 2: Generating Code
  • Java's Quiet Revolution: Thriving in the Serverless Kubernetes Era
  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  1. DZone
  2. Coding
  3. Languages
  4. Shallow and Deep Java Cloning

Shallow and Deep Java Cloning

While not recommended, cloning is a viable way to copy an object. Let's dive into Shallow Cloning, Deep Cloning, how to use them both, and when to avoid them.

By 
Naresh Joshi user avatar
Naresh Joshi
DZone Core CORE ·
Updated Aug. 19, 19 · Tutorial
Likes (12)
Comment
Save
Tweet
Share
106.2K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous article, 5 Different ways to create objects in Java, I discussed five different ways (new keyword, Class.newInstance() method, Constructor.newInstance() method, cloning and serialization-deserialization) a developer can 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 the given object to that new object.

In order to create a clone of an object, we generally design our class in such a way that:

  1. Our class should implement the Cloneable interface. Otherwise, the JVM will throw CloneNotSupportedException if we will call clone() on our object.
  2. Our class should have a 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(). Actually we are not overriding the Object.clone() method here, so we don’t have to follow any specification. Object.clone() is protected by its definition, so, practically, child classes of Object outside the package of the Object class (java.lang) can only access it through inheritance and within itself. For details on the 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 it's super’s clone(). This chain will continue until it reaches the clone() method of the Object class. The Object.clone() method is the actual worker that creates the clone of your object, and another clone() method just delegates the call to its parent’s clone().
These steps lead to a disadvantage of the cloning strategy to copy objects because all of above steps are necessary to make cloning work. But we can choose to not do all the above steps by following 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 Cit. Both classes have:

  1. toString() to show the content of the person object.
  2. equals() and hashCode() methods to compare the objects.
  3. clone() to clone the object.

And as we can see, toString(), equals(), and hashCode() have the @Override annotation, while the clone() method doesn’t have it because we are not overriding it from clone — we are creating a new method by the name of clone(), and we can name it anything else.

class Person implements Cloneable {
    private String name; 
    private int income; 
    private City city; 

    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;
    }


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


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


    @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;
    }
}


The Person class has a reference to the 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;
    }

    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) { 
            System.out.println("Both person1 and person2 holds same object");
        }

        if (person1.equals(person2)) { 
            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 the Object.clone() method.

The Object.clone() method copies the content of the object to another object bit-by-bit, meaning the values of all instance variables from one object will get copied to instance variables of other objects.

So (person1 == person2) will evaluate false because person1 and person2 are copies of each other, but both are different objects and hold different spots in heap memory. Meanwhile, person1.equals(person2) evaluates true because both have the same content.

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

So Object.clone() will copy the address that person1.city is holding to person2.city, so now city, person1.city, and person2.city all are holding the same city object. That’s why (person1.getCity() == person2.getCity()) evaluates true. This behaviour of cloning is known as  Shallow Cloning

.

Types of Cloning

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

1. Shallow Cloning

This is the default cloning strategy provided by Object.clone(), which we have seen. The clone() method of the Object class creates a new instance and copies all fields of the Cloneable object to that new instance (either it is primitive or a reference). So in the case of reference types, only reference bits get 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 suggests, deep cloning means copying 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 a Cloneable interface and overriding the clone() method in every reference type we have in our object hierarchy. Then, we call super.clone() and these clone() methods in our object’s clone method.


So we can change the clone method of the Person class in the following 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 the clone() method of the Person class, we are cloning the city object and assigning it to the new cloned person object.

In the example below, we have deep-copied the city object by implementing clone() in the City class and calling that clone() method of a person class, That's why person1.getCity() == person2.getCity() evaluates false — because both are separate objects. But we have not done the same with the Country class, person1.getCountry() == person2.getCountry() evaluates 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);


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


        if (person1.equals(person2)) {
            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");
        }


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


        city.setName("Pune");
        country.setName("IN");


        System.out.println(person1);

        System.out.println(person2);
    }
}

class Person implements Cloneable {
    private String name; 

    private int income; 
    private City city; 

    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;
    }


    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;
    }
}


Conclusion

Despite everything you've learned here, Java cloning is not considered a good way to copy an object, and there are lots of other ways to do the same. For instance, you can read Java Cloning — Copy Constructor versus Cloning, 

to get more knowledge on why Java cloning is not preferred and what you can do instead.

Cloning Object (computer science) Java (programming language)

Published at DZone with permission of Naresh Joshi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case
  • Singleton: 6 Ways To Write and Use in Java Programming

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!