【问题标题】:Effective Java Item 11: Override clone Judiciously有效的 Java 项目 11:明智地覆盖克隆
【发布时间】:2014-12-13 14:00:26
【问题描述】:

对于具有数组字段的类,Josh 说如果克隆方法仅返回 super.clone(),则生成的类实例将在原始字段中具有正确的值,但其数组字段将引用与原始数组相同的数组类实例。修改原来的会破坏不变量,反之亦然。

他用的是自定义Stack实现的例子,我用的是一个简单的Student类

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);
    }
}

请注意:我没有在我的克隆方法覆盖中的数组字段上调用 ​​clone()。

然后我做了:

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);
    }
}

输出:

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]

我想知道更改原始实例中的数组也会更改我的克隆中的数组,因为我在上面提到 “数组字段将引用与原始实例相同的数组”

通过我的克隆实现,我也应该看到克隆 s2 的变化。正确的实现应该是:

@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;
    }

我误解了吗?有人可以解释发生了什么吗?


谢谢
~莫希特

【问题讨论】:

    标签: java cloning effective-java


    【解决方案1】:

    通过调用s1.setMarks(new int[]{10, 10, 10});,您将创建一个全新的数组并将其引用写入s1 的变量marks。所以s1s2 指的是两个不同的数组。

    如果你有这个方法:

    public void setMark(int mark, int pos) {
        marks[pos] = mark;
    }
    

    Student 类中并执行以下代码:

    System.out.println("Updating the array element in Original...");
    s1.setMark(999, 0);
    System.out.println("S1 : " + s1);
    System.out.println("S2 : " + s2);
    

    然后你会看到,这也会影响s2

    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]
    

    (不要忘记注释s2.setMarks(new int[]{10, 29, 30});这一行,因为这一行还创建了一个新的数组引用并删除了s1s2之间的(数组)绑定)

    这种行为可以用一个“真实世界的例子”来描述:

    想象你和一个朋友拿着一根绳子,两端各有一个人。这条绳子代表你们都指的Array。如果您的朋友拉动那根绳子(更改该数组中的值),您会注意到这一点。如果你拉动那根绳子,你的朋友也会注意到这一点。

    致电s1.setMarks(new int[]{...});,您的朋友会得到一根新绳子,他会为此放下第一根绳子。如果他拉那根绳子,你不会注意到这一点,因为你们两个有不同的。通过致电s2.setMarks(new int[]{...});,您将获得一根新绳子并放下第一根绳子。这是第三个朋友Garbage Collector 的信号,因为没有人再使用它了但是这位朋友有点懒,所以不能保证他会立即这样做。

    【讨论】:

      【解决方案2】:

      int[] 类型的变量可用于封装四种不同事物中的任何一种:

      1. 永远不会被修改的数组的内容。

      2. 可能被修改的数组的内容,并且不存在变量所有者不知道的引用。

      3. 可能被修改且归他人所有的数组的标识。

      4. 可能被修改的数组的标识,它由变量的所有者拥有,但可能存在其他引用。

      clone() 方法不需要克隆第一种类型的数组,但除了轻微的性能成本外,克隆此类数组可能是无害的。但是,clone() 方法必须克隆第二种类型的数组,并且避免克隆第三种类型的数组。拥有第四类数组的对象一般不应该实现clone()

      不清楚您是否真的希望您的代码将数组视为第一种类型还是第三种类型;在这两种情况下,您的 clone 方法都不需要克隆数组。第二种模式在使用数组类型变量时最常见,但您的特定用例不适合它。

      对于每个数组类型的变量,确定这四种情况中的哪一种适用,您将清楚如何处理clone。请注意,您不能将数组分类为四种类型之一,您的代码可能已损坏,您应该在担心clone 之前修复它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-24
        • 1970-01-01
        • 1970-01-01
        • 2019-04-30
        • 1970-01-01
        • 2011-12-17
        相关资源
        最近更新 更多