浅克隆和深度克隆

时间:2016-08-31 07:12:54

标签: java cloning

我正在重新审视Java的一些基本方面,并对浅层克隆和深度克隆有一些疑问。我详细了解它们并了解内部结构。但我偶然发现了这个简单的练习 -

系类 -

public class Department {
    String deptName;

    public String getdName() {
        return deptName;
    }

    public void setdName(String dName) {
        this.deptName = dName;
    }

    public Department() {
        super();
    }

    public Department(String dName) {
        super();
        this.deptName = dName;
    }
}

员工类 -

public class Employee implements Cloneable{
    int empNo;
    String empName;
    Department dept;

    public int getEmpNo() {
        return empNo;
    }
    public void setEmpNo(int empNo) {
        this.empNo = empNo;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public Department getDept() {
        return dept;
    }
    public void setDept(Department dept) {
        this.dept = dept;
    }
    public Employee(int empNo, String empName, Department dept) {
        super();
        this.empNo = empNo;
        this.empName = empName;
        this.dept = dept;
    }
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

主要班级 -

public class ShalowCopyTest {

    public static void main(String args[]) {
        Department dept1 = new Department("Development");
        Department dept2 = new Department("Testing");
        Employee emp1 = new Employee(10, "Peter", dept1);
        Employee emp2 = null;
        try {
            emp2 = (Employee) emp1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        System.out.println(emp1.getDept().getdName());
        System.out.println(emp2.getDept().getdName());

        System.out.println("Now changing emp1.dept");
        //emp1.setDept(dept2); //This is deep cloning - why?
        emp1.getDept().setdName("Testing"); //This is shallow cloning
        System.out.println(emp1.getDept().getdName());
        System.out.println(emp2.getDept().getdName());
    }
}

你可以看到,如果我使用emp1.setDept(dept2),更改emp1的dept对emp2没有影响。所以这是深度克隆。但是,通过emp1.getDept()。setdName(“Testing”)更改dName也会使emp2的dName更改。所以这是浅层克隆。为什么这两条线有所作为?为什么他们不一样? 感谢。

3 个答案:

答案 0 :(得分:1)

你的例子是一个浅薄的,现在是一个很深的副本,它似乎只有你有一个深拷贝。让我试着解释一下原因:

您正在克隆的对象(Employee)确实具有复杂的数据类型(Department)。如果克隆包含其他对象的对象,则只复制引用而不是实际对象。

让我们来看看你的代码:

emp1.setDept(dept2)

在这种情况下,您不会更改实际实例(这会对克隆和原始实例产生影响),但您要将新对象分配给dept emp1实例。因此,您现在在deptemp1中有emp2的不同实例。这就是为什么你似乎有一个深刻的副本。

假设您在部门内部有另一个实例变量,其中包含所有必要的方法:

public class Department {
    String deptName;
    int someNumber;

    public void setNumber(int number){
    someNumber=number;
    }

    public int getNumber(){
    return someNumber
    }


    public Department(String dName, int number){
    super();
    deptName = dName;
    someNumber = number;
    }
    }

在主要的方法中,你可以这样做:

Department dept1 = new Department(“Development”, 50);
Employee emp1 = new Employee(10, “Peter”, dept1);
Employee emp2 = null;
emp2 = (Employee) emp1.clone();
System.out.println(emp1.getDept().getNumber; // Output: 50
System.out.println(emp1.getDept().getNumber; // Output: 50
//Now we change the instance variable of dep1 inside emp1
emp1.getDept().setNumber(100);
//Now print the numbers of both employees again
System.out.println(emp1.getDept().getNumber; // Output: 100
System.out.println(emp1.getDept().getNumber; // Output: 100

正如您所看到的,它是一个浅而非深的副本。 emp1.getDept().setdName(“Testing”)只是改变原始而不是克隆的唯一原因是因为字符串是不可变的。每次更改String时都会返回一个新的String实例,因此在克隆对象后它对原始String实例没有任何影响。

要使您的示例成为深层副本,您必须调整克隆方法,如下所示: (我做了一些小调整,所以数据类型是匹配的,你不再需要try / catch了)

@Override
public Employee clone(){
Employee clone = null;

try{
clone = (Employee)super.clone();
clone.Department = Department.clone(); // This is the important line!! You need to clone the Department Object aswell. 
}catch (CloneNotSupportedException e){
e.printStackTrace();
    }
return clone;
}

当然,如果您的Department Class也能使其工作,您必须在里面实现clonable接口和clone方法。

答案 1 :(得分:1)

由于您使用super.clone()superObject,因此您会得到一个浅色克隆,其中每个字段都会被复制。

所以如果你从:

开始

emp1 - > dept1 - > dname1

然后emp2 = emp2.clone(),你得到:

emp1 - > dept1 - > dname1             ^    emp2 ---- /

即。他们都指向同一个Department对象。

如果你做dept1.name = dname2,那么你只会影响dept1,所以你得到:

emp1 - > dept1 - > dname2             ^    emp2 ---- /

...和emp1.getDepartment().name = dname2具有完全相同的效果 - 无论你如何到达对象都无关紧要。

如果你现在emp2.department = dept2,它不会影响emp1,所以你最终得到:

emp1 - > dept1 - > ...... //不变    emp2 - > dept2 - > ...... //新分配的

要进行深度克隆,您需要编写自己的克隆例程,克隆每个级别。

这很容易出错。最好养成使用不可变对象的习惯,其中浅拷贝“只是工作” - 因为你永远不能改变一个字段,你不会意外地影响一个与你正在使用的对象共享同一个对象的对象

答案 2 :(得分:1)

我注意到您使用与此链接相同的示例:A Guide to Object Cloning in Java

可能你已经知道super.clone()的默认实现是浅层复制而不是深层复制。

在这种情况下,两个对象实例:emp1和emp2将包含指向内存中相同位置的引用。 引用Department实例,这是它在克隆emp1对象后在内存中引用的方式: enter image description here 两者都引用内存中的相同位置,它包含一个不可变的字符串:“Development”。

通过设置:

emp1.setDept(dept2);

它将emp1的引用更改为新对象dept2: enter image description here

在这种情况下,您会将其视为深层复制,但实际上并非如此。只是emp1改变了对内存中新位置的引用。

在另一种情况下,当你设置:

emp1.getDept().setdName("Testing");

您将把Departman的名称更改为“Testing” - 这两个对象实例都可以看到。

如果您有任何其他问题,请与我们联系。