Creating a Deep vs Shallow Copy of an Object in Java

·

3 min read

Creating copies of an object is one of the fundamental concepts of Java as an object-oriented programming language. This process of duplicating an object is referred to as cloning. Depending on how you may have cloned an object, the operation you perform on one object will or will not affect its duplicates.

This tutorial will guide you through the concepts of cloning objects by deep and shallow copy methods. You will learn the differences between both methods and practical illustrations of how to implement each of them.

Shallow Copy

Shallow copying an object involves pointing the duplicate object to the reference of the original object. This means that if the value of one of its referenced data type fields changes, then the duplicate objects will automatically receive this change because they share the same value.

Given the Product class declared below, by assigning the local variable - images in the shallowCopy method to the instance variable, the instance variable points to the reference of the local variable in memory. This implies that any changes you make to the values of the original list will affect its duplicate and vice versa.

public class Product{

    private List<String> images;

    public List<String> getImages() {
        return images;
    }

    public void shallowCopy(List<String> images) {
        this.images = images;
    }
}

Here’s an example of how you’d implement a shallow copy of an object in your application:

@Test
    void testShallowCopy(){

        // Given that

        List<String> originalImages = new ArrayList<>();
        originalImages.add("firstImage");
        originalImages.add("secondImage");
        originalImages.add("thirdImage");

        Products product = new Products();
        product.shallowCopy(originalImages);

        // Before modifying the original list
        assertEquals(product.getImages().size(), 3);
        assertEquals(originalImages.size(), 3);

        // when 
        originalImages.remove(2);

        // after modifying the original list
        assertNotEquals(product.getImages().size(), 3);
        assertNotEquals(originalImages.size(), 3);
    }

The above test demonstrates that by removing an item from the originalImages list, the change is also reflected in the image list of the product. It is important to note that this can result in unexpected behaviours in your application when you make changes to objects that have been shallow-copied without catering to the effect it will have on the original object or other existing duplicates.

Deep Copy

When you copy the actual values and not the references of an object’s fields to a new object, the process is referred to as deep copying. The values of the original objects’ fields are independent of those of the duplicate objects. This means that the newly copied object is an exact copy of the original, however, any changes you make on one object do not affect the other and vice versa as earlier demonstrated in the shallow copy example. Based on the Product class declared earlier, you can create a method that performs the deep copy through the below implementation:

public void deepCopy(List<String> images) {
        this.images = new ArrayList<>();
      for (String image: images) {
          this.images.add(image);
        }
    }

The deepCopy method above iterates through the original images list and adds each content to the instance variable list. This implementation does not connect the reference of the original images’ values to the copy, rather it simply takes a copy of that value and adds it to the variable. Hence, as demonstrated in the test below, when you modify the content of either the original list, the change does not reflect in the cloned list and vice versa:

@Test
    void testShallowCopy(){
        List<String> originalImages = new ArrayList<>();
        originalImages.add("firstImage");
        originalImages.add("secondImage");
        originalImages.add("thirdImage");

        Products product = new Products();
        product.deepCopy(originalImages);
        assertEquals(product.getImages().size(), 3);
        assertEquals(originalImages.size(), 3);
        originalImages.remove(2);
        assertEquals(product.getImages().size(), 3);
        assertNotEquals(originalImages.size(), 3);
    }

Conclusion

As a rule of thumb, you should adopt the shallow copy either when your object contains referenced data types but you can guarantee that the objects will not be modified or when it contains only primitive data types. Otherwise, the choice of whether you want to deep copy or shallow copy an object depends on your requirements.