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:
emp1andemp2are two differentEmployeeobjects.- However, both share the same Address reference.
- When we changed
emp2.address.city, it affectedemp1too, because they point to the sameAddressobject.
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
emp1andemp2have their own Address objects. - Changing
emp2.address.citydoes not affectemp1. - This ensures true independence between the two objects.
4. Comparing Shallow Copy and Deep Copy
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Definition | Copies only object references (nested objects share same memory). | Creates a complete independent copy of object and all nested objects. |
| Memory Usage | Less memory (since inner objects are shared). | More memory (since all objects are duplicated). |
| Performance | Faster (simple reference copy). | Slower (recursive copy of all inner objects). |
| Independence | Dependent – changes in one may affect the other. | Independent – changes in one don’t affect the other. |
| Implementation | Default behavior of Object.clone() | Must override clone() to manually copy nested objects. |
| Use Case | When 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.
- Your class contains only primitive types or immutable objects (
- 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
- Not overriding clone() properly – leads to unexpected shallow copies.
- Forgetting to copy mutable references – causes shared state bugs.
- Cloning objects with circular references – can result in infinite recursion if not handled carefully.
- Using clone() with non-Cloneable fields – causes
CloneNotSupportedException.
8. Summary
| Aspect | Shallow Copy | Deep Copy |
|---|---|---|
| Copy Type | Field-by-field (references copied) | Field-by-field with new object creation |
| Nested Object | Shared | Duplicated |
| Memory | Low | High |
| Speed | Fast | Slower |
| Safety | Risky for mutable objects | Safe 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.
