Java ArrayList Deep Copy
In this article, we will look at how to perform deep copy of an ArrayList in java and also learn about shallow copy with example programs.
ArrayList clone() method

java.util.ArrayList has a clone() method which creates its copy. The new ArrayList contains all the elements of the original list as shown in the example below

ArrayList<Integer> list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
ArrayList<Integer> clone = (ArrayList<Integer>)x.clone();
System.out.println("Original List: " + list);
System.out.println("Clone List: " + clone);

Output of this code is

Original List: [1, 2, 3] Clone List: [1, 2, 3]

Both the lists are different. If any element of original list is modified, removed or a new element is added to this list, the cloned list remains unaffected.
But, this is applicable only when the data type of ArrayList elements is
1. primitives such as integers, floats etc.,
2. Objects of immutable classes such as String or wrapper classes such as Integer, Double etc.
This is because when immutable objects are modified, a new object is created, thus, the value of original object remains unaffected.

When the original list is of objects(user defined or java objects), then even though the two lists are different, changes made to an object in the original list are also reflected in the cloned list.
This is because clone() method of ArrayList creates a Shallow copy.

Shallow copy means that only the copy of list is created. Elements of the original list are not copied into the clone list. Therefore, if any object is modified in the original list, it changes the cloned list as well
Below is an example program of shallow copy of ArrayList.

Student.java

// 
public class Student {
  private String name;
  
  private int rollNo;
  
  public Student(String n, int r) {
    name = n;
    rollNo = r;
  }
  
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getRollNo() {
    return rollNo;
  }

  public void setRollNo(int rollNo) {
    this.rollNo = rollNo;
  }
  
}

Code below creates an ArrayList of Student objects, clones it and modifies the name of first employee of original list.

// define list
ArrayList<Student> students = new ArrayList<>();
// create objects
Student s1 = new Student("A", 1);
Student s2 = new Student("B", 2);
// add to list
students.add(s1);
students.add(s2);
// create a clone
List<Student> clone = (ArrayList<Student>)students.clone();
System.out.println("Name of first student of clone: " + clone.get(0).getName());
System.out.println("Modifying name of student in source list");
// modify name of original list element
students.get(0).setName("X");
System.out.println("Name of first student of clone: " + clone.get(0).getName());

Notice the output

Name of first student of clone: A
Modifying name of student in source list
Name of first student of clone: X

It changed the name of student in the clone list even though we changed only original list. This is what is meant by a shallow copy, which clone() for ArrayList performs.

Javadoc of clone() method also states,

Returns a shallow copy of this ArrayList instance.  (The elements themselves are not copied.)

Deep copy
Deep copy of an ArrayList of objects means that even if objects of source list are modified, it should not affect the objects of cloned list.
To perform deep copy, following steps need to be followed

1. Implement clone() method in the Employee class or the class type of ArrayList objects.
2. Instead of calling clone() on ArrayList, iterate over the list and add the cloned object to the new list.

Modified Student class will be

public class Student implements Cloneable {
  private String name;
  
  private int rollNo;
  
  public Student(String n, int r) {
    name = n;
    rollNo = r;
  }
  
  // clone method
  protected Object clone() throws CloneNotSupportedException {
    return (Student)super.clone();
  }
  
  public Student(String n, int r) {
    name = n;
    rollNo = r;
  }
  
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getRollNo() {
    return rollNo;
  }

  public void setRollNo(int rollNo) {
    this.rollNo = rollNo;
  }
  
}

And code to create a deep copy of ArrayList will be

// define list 
ArrayList<Student> students = new ArrayList<>(); 
// create objects 
Student s1 = new Student("A", 1); 
Student s2 = new Student("B", 2); 
// add to list 
students.add(s1); 
students.add(s2); 
// create a new list for clone 
List<Student> clone = new ArrayList<>(); 
// add elements to clone list
students.forEach(s -> {
  try {
    clone.add((Student)s.clone());
  } catch (CloneNotSupportedException e3) {
    e3.printStackTrace();
  }
});
System.out.println("Name of first student of clone: " + clone.get(0).getName()); 
System.out.println("Modifying name of student in source list"); 
// modify name of original list element 
students.get(0).setName("X"); 
System.out.println("Name of first student of clone: " + clone.get(0).getName());

Note that the clone list is a new ArrayList object and instead of calling clone() on source list, we are iterating over the list and cloning each list element.
To iterate over the list you can use Java 8 forEach method or enhanced for loop.

Output of this code is

Name of first student of clone: A
Modifying name of student in source list
Name of first student of clone: A

Clone list remains unchanged.

Deep copy for nested objects
It might happen that the ArrayList objects themselves contain other objects. In that case, we need to create a clone of the nested objects as well to perform actual deep cloning.
Example, suppose the Student class also has a Date field(which is mutable) as below.

public class Student implements Cloneable {
  private String name;
  
  private int salary;
  
  private Date dateOfBirth;
  
  public Employee(String n, int s, Date d) {
    name = n;
    salary = s;
    dateOfBirth = d;
  }
 
  // getter and setter methods
}

So, if the value of this date is modified in an original list element, it is reflected in the cloned list as well.
To overcome this, we need to clone all the mutable fields in the clone() method that we provide in our class. So, the modified clone() method will be

// clone method 
protected Object clone() throws CloneNotSupportedException { 
  Student s = (Student)super.clone(); 
  // clone mutable object
  s.setDateOfBirth(dateOfBirth.clone());
  return s;
}

Now, even if we modify date field of original list, the corresponding value of cloned list will be unaffected. Example,

// define list 
ArrayList<Student> students = new ArrayList<>(); 
// create a date
Date d = new Date(2000, 11, 31);
// create objects 
Student s1 = new Student("A", 1, d); 
Student s2 = new Student("B", 2, d); 
// add to list 
students.add(s1); 
students.add(s2); 
// create a new list for clone 
List<Student> clone = new ArrayList<>(); 
// add elements to clone list
students.forEach(s -> {
  try {
    clone.add((Student)s.clone());
  } catch (CloneNotSupportedException e3) {
    e3.printStackTrace();
  }
});
// create a different date
Date d1 = new Date(1999, 11, 31);
System.out.println("Modifying date in source list"); 
// modify date of original list element 
students.get(0).setDateOfBirth(d1); 
System.out.println("Date of birth of original list: " + students.get(0).getDateOfBirth());
System.out.println("Date of birth of clone: " + clone.get(0).getDateOfBirth());

This prints

Date of birth of original list: Mon Dec 31 00:00:00 IST 3900
Date of birth of clone: Sun Dec 31 00:00:00 IST 3899

Look, both values are different.
Click the clap if the article was helpful.