Cloning in java
The Prototype Design Pattern is a creational design pattern that allows us to create new objects by cloning existing ones instead of building them from scratch. This is particularly useful when object creation is costly, time-consuming, or complex.
In Java, cloning is usually implemented with the Cloneable interface and the clone() method. But there’s a catch: you can either perform Shallow Cloning or Deep Cloning. The difference between them is subtle but extremely important.
This blog will guide you step by step through theory, practical implementations, comparisons, pitfalls, best practices, and real-world use cases of shallow and deep cloning in the Prototype Design Pattern.
Why Cloning?
Consider situations like:
- A game engine cloning multiple enemies with the same attributes.
- A document editor duplicating shapes or templates.
- A large enterprise application caching objects instead of rebuilding them.
In all these cases, it is inefficient to create new objects from scratch. Instead, you clone an existing prototype.
Shallow Cloning
Definition
Shallow cloning creates a new object, but nested objects are not copied. Instead, their references are shared.
- Primitive fields → copied directly.
- Object references → shared (point to the same object).
Example
class Address {
String city;
String state;
public Address(String city, String state) {
this.city = city;
this.state = state;
}
}
class Employee implements Cloneable {
int id;
String name;
Address address;
public Employee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // Shallow copy
}
}
public class ShallowCloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("Delhi", "Delhi");
Employee emp1 = new Employee(101, "Saumya", addr);
Employee emp2 = (Employee) emp1.clone();
System.out.println("Before modification:");
System.out.println("emp1 city: " + emp1.address.city);
System.out.println("emp2 city: " + emp2.address.city);
emp2.address.city = "Mumbai"; // Modify clone’s nested object
System.out.println("\nAfter modification:");
System.out.println("emp1 city: " + emp1.address.city);
System.out.println("emp2 city: " + emp2.address.city);
}
}
Output
Before modification:
emp1 city: Delhi
emp2 city: Delhi
After modification:
emp1 city: Mumbai
emp2 city: Mumbai
👉 Drawback: Both objects point to the same Address. Changing one affects the other.
Deep Cloning
Definition
Deep cloning creates a completely independent copy of an object, including all nested objects.
- Primitive fields → copied directly.
- Nested objects → cloned recursively.
Example
class Address implements Cloneable {
String city;
String state;
public Address(String city, String state) {
this.city = city;
this.state = state;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // Creates a new Address object
}
}
class Employee implements Cloneable {
int id;
String name;
Address address;
public Employee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Employee cloned = (Employee) super.clone();
cloned.address = (Address) address.clone(); // Deep copy
return cloned;
}
}
public class DeepCloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp1 = new Employee(101, "Saumya", new Address("Delhi", "Delhi"));
Employee emp2 = (Employee) emp1.clone();
System.out.println("Before modification:");
System.out.println("emp1 city: " + emp1.address.city);
System.out.println("emp2 city: " + emp2.address.city);
emp2.address.city = "Mumbai"; // Modify clone’s nested object
System.out.println("\nAfter modification:");
System.out.println("emp1 city: " + emp1.address.city);
System.out.println("emp2 city: " + emp2.address.city);
}
}
Output
Before modification:
emp1 city: Delhi
emp2 city: Delhi
After modification:
emp1 city: Delhi
emp2 city: Mumbai
👉 Success: Now emp1 and emp2 are independent.
Other Approaches to Deep Cloning
Apart from Cloneable, there are alternative techniques:
1. Copy Constructors
class Employee {
int id;
String name;
Address address;
public Employee(Employee other) {
this.id = other.id;
this.name = other.name;
this.address = new Address(other.address.city, other.address.state);
}
}
✔️ Easy to read, safe, explicit.
2. Serialization-based Cloning
import java.io.*;
class SerializationUtils {
@SuppressWarnings("unchecked")
public static T clone(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(object);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
return (T) in.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
✔️ Works well when all classes are Serializable.
❌ Slower compared to clone().
3. Apache Commons Lang
import org.apache.commons.lang3.SerializationUtils;
Employee emp2 = SerializationUtils.clone(emp1);
✔️ Convenient for enterprise use.
✔️ Requires dependency (commons-lang3).
Shallow vs Deep Cloning – Side-by-Side
Feature | Shallow Cloning | Deep Cloning |
---|---|---|
Primitive fields | Copied directly | Copied directly |
Nested objects | References shared | Duplicated recursively |
Independence | Not independent | Fully independent |
Performance | Faster, lightweight | Slower, more resource-heavy |
Memory usage | Less | More |
Use case | Simple/immutable objects | Complex/mutable/nested objects |
Pitfalls to Avoid
- Forgetting to deep clone nested objects → causes shared state issues.
- Cyclic references → can cause infinite recursion in deep cloning.
- Misusing Cloneable → Java’s clone() is tricky and not enforced strictly.
- Over-cloning → unnecessary deep copies can hurt performance.
- Serialization overhead → may slow down large-scale systems.
Best Practices
- Use shallow cloning for immutable/simple objects.
- Use deep cloning for mutable and complex objects.
- Prefer copy constructors for clarity and maintainability.
- Use libraries (Apache Commons, ModelMapper) in large applications.
- Always document whether your clone is shallow or deep.
Real-World Applications of Prototype Pattern
- Game Development → clone enemies or characters.
- Graphics & Document Editors → duplicate shapes, templates, or slides.
- Machine Learning → duplicate model configurations.
- Caching → serve clients with object copies.
- UI Frameworks → clone reusable components.
Final Thoughts
The Prototype Design Pattern is a powerful way to reduce object creation costs. But its effectiveness depends on how cloning is handled:
- Shallow Cloning: Fast, simple, but risky with mutable objects.
- Deep Cloning: Safe and independent, but slower and more resource-intensive.
In practice:
- Use shallow cloning for small or immutable objects.
- Use deep cloning for nested or mutable structures.
- For enterprise systems, consider copy constructors or serialization-based approaches for safety and flexibility.
The right choice depends on your performance requirements, object complexity, and system design.