For a class with an array field Josh says if the clone method merely return super.clone(), the resulting class instance will have the correct values in primitive fields, but its array field will refer to the same array as the original class instance. Modifying the original will destroy the invariants and vice-versa.
He used the example of custom Stack implementation, I am using a simple Student class
class Student implements Cloneable {
private String name;
private int age;
private int[] marks = {90, 70, 80};
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setMarks(int[] marks) {
this.marks = marks;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
@Override
public String toString() {
return "Student - Name : " + name + " Age : " + age + " Marks : " + Arrays.toString(marks);
}
}
Please note: I didn't invoke clone() on my array field in my clone method's override.
Then I did:
public class CloningDemo {
public static void main(String[] args) {
Student s1 = new Student("Mohit", 30);
Student s2 = null;
try {
s2 = s1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println("S1 : " + s1);
System.out.println("S2 : " + s2);
System.out.println("Updating the clone...");
s2.setName("Rohit");
s2.setAge(29);
s2.setMarks(new int[]{10, 29, 30});
System.out.println("S1 : " + s1);
System.out.println("S2 : " + s2);
System.out.println("Updating the array elements in Original...");
s1.setMarks(new int[]{10, 10, 10});
System.out.println("S1 : " + s1);
System.out.println("S2 : " + s2);
}
}
Output:
S1 : Student - Name : Mohit Age : 30 Marks : [90, 70, 80]
S2 : Student - Name : Mohit Age : 30 Marks : [90, 70, 80]
Updating the clone...
S1 : Student - Name : Mohit Age : 30 Marks : [90, 70, 80]
S2 : Student - Name : Rohit Age : 29 Marks : [10, 29, 30]
Updating the array elements in Original...
S1 : Student - Name : Mohit Age : 30 Marks : [10, 10, 10]
S2 : Student - Name : Rohit Age : 29 Marks : [10, 29, 30]
I was wondering that changing array in original instance would change the array in my clone too, because I mentioned above "array field will refer to the same array as the original instance"
With my implementation of clone I should have seeing changes in the clone s2 too. The proper implementation would've been:
@Override
protected Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.marks = marks.clone(); // I am not doing this in my code.
return student;
}
Have I misunderstood this? Can someone please explain what is going on?
Thanks
~Mohit
By calling s1.setMarks(new int[]{10, 10, 10});
you're creating a completely new array and write its reference to the variable marks
of s1
. So s1
and s2
refer to two different arrays.
If you would have this method:
public void setMark(int mark, int pos) {
marks[pos] = mark;
}
in the class Student
and perform following code:
System.out.println("Updating the array element in Original...");
s1.setMark(999, 0);
System.out.println("S1 : " + s1);
System.out.println("S2 : " + s2);
then you will see, that this affects s2
too:
Updating the array elements in Original...
S1 : Student - Name : Mohit Age : 30 Marks : [999, 70, 80]
S2 : Student - Name : Rohit Age : 29 Marks : [999, 70, 80]
(don't forget to comment the line s2.setMarks(new int[]{10, 29, 30});
, as well, because this line also creates a new array reference and removes the (array) binding between s1
and s2
)
This behavior could be described with a "real world example":
Image you and a friend are holding a rope, with one person on each end. This rope represents the Array
you're both referring to. If your friend pulls that rope (changing a value in that array), you'll notice that. And if you pull that rope, your friend will notice that, too.
By calling s1.setMarks(new int[]{...});
your friend gets a new rope and he will drop the first one for it. If he pulls that rope, you won't notice that, because you two have different ones. By calling s2.setMarks(new int[]{...});
you will get a new rope and drop the first one, as well. This is the signal for the third friend, called Garbage Collector
, to take that rope and dump it, because no one is using it anymore. But this friend is kind of lazy, so there is no guarantee that he will do that immediately.