Learnitweb

Shallow Copy vs Deep Copy in Java

In Java, copying an object is a common operation, especially when working with mutable classes, collections, or when implementing features like cloning, caching, or undo mechanisms. However, not all copies are the same. The way objects are copied determines whether the new object shares the same references or gets completely independent copies of its data.

To understand this behavior, we categorize object copying into two types:

  • Shallow Copy
  • Deep Copy

Let’s explore both in detail with explanations, examples, and visual representations.


1. Understanding Object Copying in Java

In Java, when you assign one object to another, you’re copying only the reference (i.e., memory address), not the actual data.
For example:

Employee e1 = new Employee("Alice", new Address("New York"));
Employee e2 = e1;  // Copies only reference

Here, both e1 and e2 refer to the same object in memory. Changing one affects the other.
To create a separate object, we must perform object cloning — either shallow or deep.


2. What is a Shallow Copy?

A shallow copy creates a new object but copies the references of nested (non-primitive) objects rather than duplicating them.
That means the outer object is different, but the inner objects (fields) still point to the same memory location.

Example: Shallow Copy Using clone()

class Address {
    String city;

    Address(String city) {
        this.city = city;
    }
}

class Employee implements Cloneable {
    String name;
    Address address;

    Employee(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // performs shallow copy
    }
}

public class ShallowCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Employee emp1 = new Employee("Alice", address);
        Employee emp2 = (Employee) emp1.clone();

        emp2.name = "Bob";
        emp2.address.city = "Los Angeles";

        System.out.println(emp1.name + " - " + emp1.address.city);
        System.out.println(emp2.name + " - " + emp2.address.city);
    }
}

Output:

Alice - Los Angeles
Bob - Los Angeles

Explanation:

  • emp1 and emp2 are two different Employee objects.
  • However, both share the same Address reference.
  • When we changed emp2.address.city, it affected emp1 too, because they point to the same Address object.

This demonstrates a shallow copy.


3. What is a Deep Copy?

A deep copy creates a new object along with copies of all the nested objects.
So, the new object is completely independent of the original — modifying one doesn’t affect the other.

Example: Deep Copy Implementation

class Address {
    String city;

    Address(String city) {
        this.city = city;
    }

    // Copy constructor for deep copy
    Address(Address address) {
        this.city = address.city;
    }
}

class Employee implements Cloneable {
    String name;
    Address address;

    Employee(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // Deep copy using clone
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Employee cloned = (Employee) super.clone();
        cloned.address = new Address(this.address); // create a new Address object
        return cloned;
    }
}

public class DeepCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Employee emp1 = new Employee("Alice", address);
        Employee emp2 = (Employee) emp1.clone();

        emp2.name = "Bob";
        emp2.address.city = "Los Angeles";

        System.out.println(emp1.name + " - " + emp1.address.city);
        System.out.println(emp2.name + " - " + emp2.address.city);
    }
}

Output:

Alice - New York
Bob - Los Angeles

Explanation:

  • Both emp1 and emp2 have their own Address objects.
  • Changing emp2.address.city does not affect emp1.
  • This ensures true independence between the two objects.

4. Comparing Shallow Copy and Deep Copy

FeatureShallow CopyDeep Copy
DefinitionCopies only object references (nested objects share same memory).Creates a complete independent copy of object and all nested objects.
Memory UsageLess memory (since inner objects are shared).More memory (since all objects are duplicated).
PerformanceFaster (simple reference copy).Slower (recursive copy of all inner objects).
IndependenceDependent – changes in one may affect the other.Independent – changes in one don’t affect the other.
ImplementationDefault behavior of Object.clone()Must override clone() to manually copy nested objects.
Use CaseWhen object has only primitive fields or immutables.When object contains mutable nested objects.

5. Alternative Approaches to Deep Copy

While clone() is one option, there are other ways to achieve deep copies in Java.

a. Using Copy Constructors

This is often the preferred modern approach, as it avoids Cloneable complexity.

class Employee {
    String name;
    Address address;

    Employee(String name, Address address) {
        this.name = name;
        this.address = new Address(address);
    }

    // Copy constructor
    Employee(Employee other) {
        this.name = other.name;
        this.address = new Address(other.address);
    }
}

b. Using Serialization

Serialization can be used to perform deep copies by serializing and deserializing the object.

import java.io.*;

class DeepCopyUtil {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepCopy(T object) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(object);
            out.flush();
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            return (T) in.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

This method works well for complex object graphs but is slower due to serialization overhead.


6. When to Use Each

  • Use Shallow Copy when:
    • Your class contains only primitive types or immutable objects (String, Integer, etc.).
    • Performance is a critical factor and shared references are acceptable.
  • Use Deep Copy when:
    • Your class contains mutable nested objects.
    • You need complete independence between original and cloned instances.
    • You are implementing data duplication features (e.g., undo functionality, snapshot, etc.).

7. Common Mistakes

  1. Not overriding clone() properly – leads to unexpected shallow copies.
  2. Forgetting to copy mutable references – causes shared state bugs.
  3. Cloning objects with circular references – can result in infinite recursion if not handled carefully.
  4. Using clone() with non-Cloneable fields – causes CloneNotSupportedException.

8. Summary

AspectShallow CopyDeep Copy
Copy TypeField-by-field (references copied)Field-by-field with new object creation
Nested ObjectSharedDuplicated
MemoryLowHigh
SpeedFastSlower
SafetyRisky for mutable objectsSafe and isolated

In conclusion, understanding shallow vs deep copy is essential for writing safe and predictable Java programs, especially when dealing with mutable data structures or designing cloneable entities.