Shallow vs. Deep Object Copying

JavaJavaBeginner
Practice Now

Introduction

A shallow copy will simply copy the values of the fields of the original object into the new object. In shallow copy, if the original object has any references to other class objects, then only the reference of that object is cloned. Therefore, any changes in this referenced object will be reflected in both the original and the cloned object. However, a deep copy creates a new object, including all referenced objects, and any changes made to the cloned object will not affect the original object.

Creating the Object to Be Copied

We will first create the Student class that has a member variable of type GPA(user-defined class) to store the student's GPA.

class GPA {
    int firstYear;
    int secondYear;

    public GPA(int firstYear, int secondYear) {
        this.firstYear = firstYear;
        this.secondYear = secondYear;
    }

    public int getFirstYear() {
        return this.firstYear;
    }

    public int getSecondYear() {
        return this.secondYear;
    }

    public void setFirstYear(int firstYear) {
        this.firstYear = firstYear;
    }

    public void setSecondYear(int secondYear) {
        this.secondYear = secondYear;
    }
}

class Student {
    private String name;
    private GPA gpa;

    public Student(String name, GPA gpa) {
        this.name = name;
        this.gpa = gpa;
    }

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

    public GPA getGpa() {
        return this.gpa;
    }

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

    public void setGpa(GPA gpa) {
        this.gpa = gpa;
    }
}

Implementing Cloneable Interface and Overriding the clone() Method for Shallow Copy

In order to implement shallow copy, we will use the clone() method provided by the Object class. The Cloneable interface must be implemented to use the clone() method because it is a protected method. We will simply call the clone() method in the Student class to perform shallow cloning.

class Student implements Cloneable {
    // ...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Implementing a Deep Copy Using the Clone() Method

We can make a deep copy by overriding the clone() method as shown below. In the clone() method, we will create a new object using the new keyword and assign the values of the original object to the new object. Once we have a new object, we will create new objects for all referenced classes that don't contain primitive types and assign new references instead of the original ones.

class Student implements Cloneable {
    // ...

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student newStudent = (Student)super.clone();
        newStudent.gpa = (GPA)this.gpa.clone();
        return newStudent;
    }
}

class GPA implements Cloneable {
    // ...

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

Implementing Copy Constructor

Another way to create a deep copy is to use a copy constructor. A copy constructor is a constructor that takes an object of the same class as input and returns a new object with the same values but new references. We will create a copy constructor for the Student class.

class Student {
    // ...

    public Student(Student student) {
        this(student.getName(), new GPA(student.getGpa().getFirstYear(), student.getGpa().getSecondYear()));
    }
}

Implementing Serialization

Serialization provides a way to create a deep copy as well. By serializing the object and then deserializing it, we can essentially produce a new object with new references but the same values. To do this, we must implement the Serializable interface in the classes that require deep copying.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class GPA implements Serializable {
    // ...
}

class Student implements Serializable {
    // ...

    public Student deepCopyUsingSerialization() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            return (Student)ois.readObject();
        } catch(Exception e) {
            return null;
        }
    }
}

Testing

Now we can test our different methods of deep copying as shown in the following code.

public static void main(String[] args) {
    GPA gpa = new GPA(7, 8);
    Student original = new Student("John", gpa);

    // Shallow copy
    Student shallowCopy = null;
    try {
        shallowCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Deep copy using clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Deep copy using copy constructor
    Student constructorCopy = new Student(original);

    // Deep copy using serialization
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Test for deep copying
    System.out.println("Original object: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Shallow copy: \t\t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Clone copy: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Copy constructor copy: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Serialization copy: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nAfter changing cloneCopy's GPA field: ");
    System.out.println("Original object: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Clone copy: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Running

After executing the command below:

javac Lab.java && java Lab

You will see the following output:

Original object: 		7 8
Shallow copy: 			7 8
Clone copy: 			7 8
Copy constructor copy: 	7 8
Serialization copy: 		7 8

After changing cloneCopy's GPA field:
Original object: 		7 8
Clone copy: 			10 9

Summary

In this lab, we have learned how to make a deep copy of an object in Java. A shallow copy simply copies the field values from the original object to the new object, while deep copying creates a new object with new, separate references for all included objects. We can make a deep copy by using the clone() method, copy constructors, or serialization. It's important to make deep copies to avoid unexpected behavior when working with objects that have referenced classes.

Other Java Tutorials you may like