Object Cloning Mechanisms in Java: Shallow vs. Deep Copy
Reference Assignment
Assigning an object to a new variable does not instantiate a new object in memory; it merely duplicates the reference pointing to the same memory address.
public class ReferenceDemo {
public static void main(String[] args) {
Employee emp1 = new Employee("Alice", 101);
Employee emp2 = emp1;
emp2.setFullName("Bob");
emp2.setId(102);
System.out.println(emp1);
System.out.println(emp2);
}
}
class Employee {
private String fullName;
private int id;
public Employee(String fullName, int id) {
this.fullName = fullName;
this.id = id;
}
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Override
public String toString() {
return "Employee{fullName='" + fullName + '\'', id=" + id + '}';
}
}
Output:
Employee{fullName='Bob', id=102}
Employee{fullName='Bob', id=102}
After assigning emp1 to emp2, modifying the properties of emp2 also alters emp1. This occurs because both variables reference the identical memory location.
Shallow Cloning
Shallow cloning creates a new instance of the object and copies its primitive fields. However, reference type fields are not duplicated; only their memory addresses are copied, meaning both the original and cloned objects share the same nested objects.
To enable shallow cloning, a class must implement the Cloneable interface and override the clone() method.
public class ShallowCloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Employee original = new Employee("Alice", 101, new Department("Engineering"));
Employee cloned = (Employee) original.clone();
cloned.setFullName("Bob");
cloned.setId(102);
cloned.getDepartment().setDeptName("Marketing");
System.out.println(original);
System.out.println(cloned);
}
}
class Employee implements Cloneable {
private String fullName;
private int id;
private Department department;
public Employee(String fullName, int id, Department department) {
this.fullName = fullName;
this.id = id;
this.department = department;
}
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Employee{fullName='" + fullName + '\'', id=" + id + ", department=" + department + '}';
}
}
class Department {
private String deptName;
public Department(String deptName) { this.deptName = deptName; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
@Override
public String toString() { return "Department{deptName='" + deptName + '\''}'; }
}
Output:
Employee{fullName='Alice', id=101, department=Department{deptName='Marketing'}}
Employee{fullName='Bob', id=102, department=Department{deptName='Marketing'}}
Changing the primitive wrapper (fullName) and primitive (id) fields in the cloned object does not affect the original. However, altering the nested Department object inside the clone mutates the original object's Department as well, proving that both references point to the same nested instance.
Deep Cloning
Deep cloning generates a completely independent copy of the object graph. Both primitive and reference type fields are duplicated into new memory allocations.
Implementing the Cloneable Interface
This approach requires manually cloning each reference type within the overridden clone() method. The nested classes must also implement Cloneable.
public class DeepCloneManualDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Employee original = new Employee("Alice", 101, new Department("Engineering"));
Employee cloned = (Employee) original.clone();
cloned.setFullName("Bob");
cloned.setId(102);
cloned.getDepartment().setDeptName("Marketing");
System.out.println(original);
System.out.println(cloned);
}
}
class Employee implements Cloneable {
private String fullName;
private int id;
private Department department;
public Employee(String fullName, int id, Department department) {
this.fullName = fullName;
this.id = id;
this.department = department;
}
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Override
protected Object clone() throws CloneNotSupportedException {
Employee clonedEmp = (Employee) super.clone();
clonedEmp.department = (Department) this.department.clone();
return clonedEmp;
}
@Override
public String toString() {
return "Employee{fullName='" + fullName + '\'', id=" + id + ", department=" + department + '}';
}
}
class Department implements Cloneable {
private String deptName;
public Department(String deptName) { this.deptName = deptName; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() { return "Department{deptName='" + deptName + '\''}'; }
}
Output:
Employee{fullName='Alice', id=101, department=Department{deptName='Engineering'}}
Employee{fullName='Bob', id=102, department=Department{deptName='Marketing'}}
Modifying the Department in the cloned object no longer impacts the original, confirming a true deep copy. This method becomes tedious with deeply nested object graphs.
Implementing the Serializable Interface
Serialization provides a robust alternative for deep copying. By writing an object to a byte stream and reading it back, the JVM constructs entirely new instances across the entire object graph. All related classes must implement Serializable.
import java.io.*;
public class DeepCloneSerializationDemo {
public static void main(String[] args) {
Employee original = new Employee("Alice", 101, new Department("Engineering"));
Employee cloned = original.createDeepCopy();
cloned.setFullName("Bob");
cloned.setId(102);
cloned.getDepartment().setDeptName("Marketing");
System.out.println(original);
System.out.println(cloned);
}
}
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String fullName;
private int id;
private Department department;
public Employee(String fullName, int id, Department department) {
this.fullName = fullName;
this.id = id;
this.department = department;
}
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public Employee createDeepCopy() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Employee) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep copy via serialization failed", e);
}
}
@Override
public String toString() {
return "Employee{fullName='" + fullName + '\'', id=" + id + ", department=" + department + '}';
}
}
class Department implements Serializable {
private static final long serialVersionUID = 1L;
private String deptName;
public Department(String deptName) { this.deptName = deptName; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
@Override
public String toString() { return "Department{deptName='" + deptName + '\''}'; }
}
Output:
Employee{fullName='Alice', id=101, department=Department{deptName='Engineering'}}
Employee{fullName='Bob', id=102, department=Department{deptName='Marketing'}}
The serialization technique yields the same fully independent clone, effectively resolving the complexities of deeply nested references without requiring manual clone() method implementations for every sub-object.