Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Object Cloning Mechanisms in Java: Shallow vs. Deep Copy

Tech May 13 1

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.

Tags: Java

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.