Java Core Concepts: Deep copy vs shallow copy

  • Post last modified:April 30, 2023
  • Reading time:4 mins read

Explaining deep copy and shallow copy with examples

Introduction

  • Creating a copy of an object is a very common operation in software development. Depending on the structure of the copy, it can be a Deep copy or a Shallow copy. 
  • In this article, we will discuss what is the difference between the two in the context of Java.
  • Before discussing the difference between Shallow vs Deep Copy, let’s first understand what is the difference between Value copy vs Reference Copy.

Value vs Reference Copy

  • Let’s consider we have the object base_config that contains all the basic configurations for the environment.
  • Now this object will work as the template in order to construct environment-specific configuration. If we want to create a Dev configuration then we will pass dev-related configs and merge them to the base config.
  • In many programming languages such as Java, if you pass an object, it’s usually a passing reference of the object. By doing this, we don’t need to copy actual values and pass the whole object, instead, we just pass the reference and another object can read the values.
  • This becomes a problem when we need to mutate/change the object that was passed to us since more than one object refers to a passed object and change made by one object can interfere with another object.
  • Instead of passing a reference of the object, another way is to pass the value of the object, so that each dependent object gets a copy of the value and not the reference. Now if one of the dependent objects changes the passed value this will not interfere with another dependent object.
  • Now that we know the reference copy and value copy let’s continue with shallow copy and deep copy

Shallow Copy

  • Now, let’s copy all the values of the properties of the target object i.e config
  • Now In Java, each object can contain primitive and reference types. So while shallow copying for the primitive type, we copy the value of the object, but for reference types, we copy the value of the reference, which means we perform reference copy.
  • So in the case of reference copy, we can end up modifying the state of the original object, which can be a side effect.
  • In the below diagram, while shallow copying config, for Id since its primitive type we get the value, for name and version since its String type and String is immutable we get the value copy. But for the values object, we get the reference copy, hence shallow copy and original both refer to the same object in memory.

Deep Copy

  • Now, let’s copy all the values of the properties of the target object i.e config.
  • While in deep copy, we intentionally copy all the values for both primitive and reference types. When we encounter a reference type we create a new instance from the values of the reference type, instead of just copying the reference.
  • By deep copying, there is no shared state that exists between an object and its copy, hence we can freely mutate the state without side effects.
  • Now that we have covered the theory, let’s jump to a simple exercise to validate it.

Exercise

Config Class

class Config{
    private int id;
    private String name;
    private String version;
    private List<String> values;

   // getters, setters, constructor

Shallow Copy

  • In the below example, our intention is to just modify configShallowCopy but we modified the config object along with configShallowCopy.
public static void ShallowCopyExample() {
        System.out.println("Shallow Copying Example");
        List<String> values = new ArrayList<String>(Arrays.asList("a","b","c","d"));
        Config config = new Config(1, "sample-config", "1", values);
        Config configShallowCopy = new Config(config.getId(),config.getName(),config.getVersion(),config.getValues());

        List<String> shallowValues = configShallowCopy.getValues();
        shallowValues.add("99999");    // our intention is to just modify configShallowCopy
        System.out.println("Target Object: "+config);
        System.out.println("Shallow Copy: "+configShallowCopy); // but we modified config along with configShallowCopy, due to shallow copy
        System.out.println("Target Values Object HashCode: "+config.getValues().hashCode());
        System.out.println("Shallow Copy Values HashCode: "+configShallowCopy.getValues().hashCode());
    }

Output

  • As we can see List<String>Values have the same hashcode for target vs shallow copy. Which means they both refer to the same memory.

Before You Leave

Other Must-Read Articles

Leave a Reply