Object Cloning

Introduction

Cloning creates an exact copy of an object. Java provides clone() method in Object class for creating object copies.


Why Clone?

  1. Preserve original - modify copy without affecting original
  2. Backup - keep original safe
  3. Testing - test with copy
  4. Performance - faster than creating from scratch

Cloneable Interface

Marker interface (no methods) that indicates object can be cloned.

class MyClass implements Cloneable {
    // Class can be cloned
}

Without Cloneable:

  • clone() throws CloneNotSupportedException

Basic clone() Method

class Student implements Cloneable {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Student s1 = new Student("John", 20);
            Student s2 = (Student) s1.clone();

            System.out.println("Original: " + s1);  // John (20)
            System.out.println("Clone: " + s2);     // John (20)

            // Modify clone
            s2.name = "Alice";
            s2.age = 22;

            System.out.println("\nAfter modifying clone:");
            System.out.println("Original: " + s1);  // John (20)
            System.out.println("Clone: " + s2);     // Alice (22)
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Shallow Copy vs Deep Copy

Shallow Copy:

  • Copies object and references
  • Doesn’t copy referenced objects
  • Default clone() behavior

Deep Copy:

  • Copies object and all referenced objects
  • Creates independent copy
  • Must implement manually

Shallow Copy Example

class Address {
    String city;

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

class Person implements Cloneable {
    String name;
    Address address;  // Reference type

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

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

public class Main {
    public static void main(String[] args) {
        try {
            Address addr = new Address("New York");
            Person p1 = new Person("John", addr);
            Person p2 = (Person) p1.clone();

            System.out.println("p1: " + p1.name + ", " + p1.address.city);
            System.out.println("p2: " + p2.name + ", " + p2.address.city);

            // Modify clone's address
            p2.address.city = "Boston";

            // Original also changed (shallow copy problem!)
            System.out.println("\nAfter modifying clone:");
            System.out.println("p1: " + p1.name + ", " + p1.address.city);  // Boston!
            System.out.println("p2: " + p2.name + ", " + p2.address.city);  // Boston
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Deep Copy Example

class Address implements Cloneable {
    String city;
    String country;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    String name;
    Address address;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // Deep copy - clone referenced objects too
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone();
        return cloned;
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Address addr = new Address("New York", "USA");
            Person p1 = new Person("John", addr);
            Person p2 = (Person) p1.clone();

            System.out.println("p1: " + p1.name + ", " + p1.address.city);
            System.out.println("p2: " + p2.name + ", " + p2.address.city);

            // Modify clone's address
            p2.address.city = "Boston";

            // Original unchanged (deep copy works!)
            System.out.println("\nAfter modifying clone:");
            System.out.println("p1: " + p1.name + ", " + p1.address.city);  // New York
            System.out.println("p2: " + p2.name + ", " + p2.address.city);  // Boston
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Deep Copy with Array

class Student implements Cloneable {
    String name;
    int[] marks;  // Array reference

    Student(String name, int[] marks) {
        this.name = name;
        this.marks = marks;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloned = (Student) super.clone();

        // Deep copy the array
        cloned.marks = marks.clone();

        return cloned;
    }

    void displayMarks() {
        System.out.print(name + ": ");
        for (int mark : marks) {
            System.out.print(mark + " ");
        }
        System.out.println();
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            int[] marks = {85, 90, 78};
            Student s1 = new Student("John", marks);
            Student s2 = (Student) s1.clone();

            s1.displayMarks();  // John: 85 90 78
            s2.displayMarks();  // John: 85 90 78

            // Modify clone's marks
            s2.marks[0] = 95;

            System.out.println("\nAfter modifying clone:");
            s1.displayMarks();  // John: 85 90 78 (unchanged)
            s2.displayMarks();  // John: 95 90 78
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Copy Constructor Alternative

class Student {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Copy constructor
    Student(Student other) {
        this.name = other.name;
        this.age = other.age;
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Student s1 = new Student("John", 20);
        Student s2 = new Student(s1);  // Copy using constructor

        System.out.println("Original: " + s1);
        System.out.println("Copy: " + s2);

        s2.name = "Alice";

        System.out.println("\nAfter modifying copy:");
        System.out.println("Original: " + s1);  // John (20)
        System.out.println("Copy: " + s2);     // Alice (20)
    }
}

Complete Example: Employee Cloning

class Department implements Cloneable {
    String name;
    String location;

    Department(String name, String location) {
        this.name = name;
        this.location = location;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return name + " (" + location + ")";
    }
}

class Employee implements Cloneable {
    int id;
    String name;
    double salary;
    Department department;
    String[] skills;

    Employee(int id, String name, double salary, Department department, String[] skills) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.department = department;
        this.skills = skills;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // Shallow copy first
        Employee cloned = (Employee) super.clone();

        // Deep copy reference types
        cloned.department = (Department) department.clone();
        cloned.skills = skills.clone();

        return cloned;
    }

    void display() {
        System.out.println("ID: " + id);
        System.out.println("Name: " + name);
        System.out.println("Salary: $" + salary);
        System.out.println("Department: " + department);
        System.out.print("Skills: ");
        for (String skill : skills) {
            System.out.print(skill + " ");
        }
        System.out.println("\n");
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Department dept = new Department("IT", "Building A");
            String[] skills = {"Java", "Python", "SQL"};

            Employee e1 = new Employee(101, "John", 50000, dept, skills);
            Employee e2 = (Employee) e1.clone();

            System.out.println("Original Employee:");
            e1.display();

            System.out.println("Cloned Employee:");
            e2.display();

            // Modify clone
            e2.id = 102;
            e2.name = "Alice";
            e2.salary = 55000;
            e2.department.name = "HR";
            e2.skills[0] = "JavaScript";

            System.out.println("\nAfter Modifying Clone:");
            System.out.println("Original Employee:");
            e1.display();

            System.out.println("Cloned Employee:");
            e2.display();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

CloneNotSupportedException

class MyClass {  // Not implementing Cloneable
    String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // Will throw exception
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            MyClass obj1 = new MyClass();
            MyClass obj2 = (MyClass) obj1.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("Clone not supported!");
            e.printStackTrace();
        }
    }
}

clone() Method Rules

  1. Implement Cloneable interface
  2. Override clone() method
  3. Call super.clone()
  4. Cast return type
  5. Handle exception
  6. Deep copy if needed

Comparison: Shallow vs Deep

// Shallow Copy
@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();  // Only copies references
}

// Deep Copy
@Override
protected Object clone() throws CloneNotSupportedException {
    MyClass cloned = (MyClass) super.clone();
    cloned.reference = (Type) this.reference.clone();  // Copy objects
    cloned.array = this.array.clone();  // Copy arrays
    return cloned;
}

Serialization Alternative

Deep copy using serialization:

import java.io.*;

class Student implements Serializable {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Deep copy using serialization
    Student deepCopy() {
        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 (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

When to Use Clone?

Use clone():

  • Simple objects
  • Performance critical
  • Object graph known

Avoid clone():

  • Complex inheritance
  • Many mutable fields
  • Consider copy constructor instead

Best Practices

  1. Implement Cloneable explicitly
  2. Override clone() as public
  3. Document shallow vs deep
  4. Consider alternatives (copy constructor)
  5. Test thoroughly
  6. Handle exceptions properly

Quick Reference

// Basic cloning
class MyClass implements Cloneable {
    // Fields

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();  // Shallow copy
    }
}

// Deep cloning
class MyClass implements Cloneable {
    ReferenceType ref;
    int[] array;

    @Override
    public Object clone() throws CloneNotSupportedException {
        MyClass cloned = (MyClass) super.clone();
        cloned.ref = (ReferenceType) this.ref.clone();
        cloned.array = this.array.clone();
        return cloned;
    }
}

// Usage
try {
    MyClass original = new MyClass();
    MyClass copy = (MyClass) original.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

Common Mistakes

// ✗ Not implementing Cloneable
class MyClass {
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // Exception!
    }
}

// ✗ Shallow copy for mutable objects
class Person implements Cloneable {
    Address address;  // Mutable reference

    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // Problem: shared address!
    }
}

// ✓ Correct - deep copy
class Person implements Cloneable {
    Address address;

    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) this.address.clone();
        return cloned;
    }
}

Exam Tips

Remember:

  1. clone() creates object copy
  2. Cloneable marker interface required
  3. Shallow copy - copies references
  4. Deep copy - copies objects
  5. super.clone() for cloning
  6. CloneNotSupportedException if not Cloneable
  7. Arrays need clone() for deep copy
  8. Override as public
  9. Cast return type
  10. Copy constructor alternative

Common Questions:

  • What is cloning?
  • Shallow vs deep copy?
  • How to implement clone()?
  • What is Cloneable interface?
  • When CloneNotSupportedException?
  • How to deep copy arrays?
  • Clone() vs copy constructor?
  • When to use cloning?
  • How to clone reference types?
  • Best practices for cloning?