Effective Java Item 11: Override clone Judiciously

不想你离开。 提交于 2019-12-22 11:03:40

问题


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


回答1:


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.




回答2:


A variable of type int[] may be used to encapsulate any of four different things:

  1. The contents of an array which will never be modified.

  2. The contents of an array which might be modified, and to which no references exist that the owner of the variable doesn't know about.

  3. The identity of an array which might be modified, and which is owned by someone else.

  4. The identity of an array which might be modified, and which is owned by the owner of the variable, but to which other references may exist.

A clone() method doesn't need to clone arrays of the first type, but aside from a slight performance cost cloning such arrays is likely to be harmless. A clone() method must, however, clone arrays of the second type and refrain from cloning arrays of the third type. Objects that own arrays of the fourth type should generally not implement clone().

It's not clear whether you really want your code to consider the array to be of the first type or the third; in neither case is it necessary for your clone method to clone the array. The second pattern is the most common when using array-type variables, but your particular usage case doesn't fit it.

For every array-type variable, identify which of the four cases applies and it will be clear how you should proceed with clone. Note that you can't classify an array into one of the four types, your code is probably broken, and you should fix it before worrying about clone.



来源:https://stackoverflow.com/questions/26434278/effective-java-item-11-override-clone-judiciously

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!