浅克隆和深克隆
Shallow cloning and deep cloning
我正在重新审视 Java 的一些基本方面,并对浅克隆和深度克隆有一些疑问。我详细了解它们并了解内部结构。但我偶然发现了这个简单的练习 -
部门class-
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;
}
}
员工class-
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();
}
}
主要class-
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的部门对emp2没有影响。所以这就是深度克隆。但是仅通过 emp1.getDept().setdName("Testing") 更改 dName 也会使 emp2 的 dName 发生变化。所以这是浅克隆。为什么这两条线会有所作为?为什么他们不一样?
谢谢
你的例子是一个浅拷贝,现在是一个深拷贝,看起来你只有一个深拷贝。让我试着解释一下原因:
您正在克隆的对象(员工)确实具有复杂的数据类型(部门)。如果您克隆一个包含其他对象的对象,那么您只复制引用而不是实际对象。
让我们看一下您的代码:
emp1.setDept(dept2)
在这种情况下,您并没有改变实际实例(这会对克隆和原始实例都产生影响),而是将新对象分配给 [=15] 的 dept
实例=].因此,您现在在 emp1
和 emp2
中有不同的 dept
实例。这就是为什么看起来你有一个深层副本。
假设您的部门内有另一个实例变量,其中包含所有必要的方法:
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;
}
}
在你的 main 方法中你做了类似的事情:
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;
}
当然,如果您的部门 Class 也必须在其中实现可克隆接口和克隆方法才能使其正常工作。
由于您使用的是 super.clone()
,而 super
是一个 Object
,您会得到一个浅克隆,其中每个字段都被复制。
所以如果你开始:
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 -> ... // 新分配
要进行深度克隆,您需要编写自己的克隆例程,克隆每个级别。
这很容易出错。最好养成使用不可变对象的习惯,其中浅拷贝 "just work"——因为您永远无法更改字段,所以您不会意外影响与您正在工作的对象共享同一对象的对象与.
我注意到您使用的示例与此 link 相同:A Guide to Object Cloning in Java
可能您已经知道 super.clone() 的默认实现执行浅拷贝而不是深拷贝。
在这种情况下,两个对象实例:emp1 和 emp2 将包含指向内存中相同位置的引用。
引用 Department 实例,在克隆 emp1 对象后,它在内存中的引用是这样的:
两者都引用内存中的同一个位置,其中包含一个不可变的字符串:"Development".
通过设置:
emp1.setDept(dept2);
它将 emp1 的引用更改为新对象 dept2:
在这种情况下,您会看到它是深拷贝,但实际上不是。只是 emp1 更改了它对内存中新位置的引用。
在另一种情况下,当您设置:
emp1.getDept().setdName("Testing");
您将 Departman 的姓名更改为 "Testing" - 这将对两个对象实例可见。
如果您还有其他问题,请告诉我。
我正在重新审视 Java 的一些基本方面,并对浅克隆和深度克隆有一些疑问。我详细了解它们并了解内部结构。但我偶然发现了这个简单的练习 -
部门class-
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;
}
}
员工class-
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();
}
}
主要class-
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的部门对emp2没有影响。所以这就是深度克隆。但是仅通过 emp1.getDept().setdName("Testing") 更改 dName 也会使 emp2 的 dName 发生变化。所以这是浅克隆。为什么这两条线会有所作为?为什么他们不一样? 谢谢
你的例子是一个浅拷贝,现在是一个深拷贝,看起来你只有一个深拷贝。让我试着解释一下原因:
您正在克隆的对象(员工)确实具有复杂的数据类型(部门)。如果您克隆一个包含其他对象的对象,那么您只复制引用而不是实际对象。
让我们看一下您的代码:
emp1.setDept(dept2)
在这种情况下,您并没有改变实际实例(这会对克隆和原始实例都产生影响),而是将新对象分配给 [=15] 的 dept
实例=].因此,您现在在 emp1
和 emp2
中有不同的 dept
实例。这就是为什么看起来你有一个深层副本。
假设您的部门内有另一个实例变量,其中包含所有必要的方法:
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;
}
}
在你的 main 方法中你做了类似的事情:
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;
}
当然,如果您的部门 Class 也必须在其中实现可克隆接口和克隆方法才能使其正常工作。
由于您使用的是 super.clone()
,而 super
是一个 Object
,您会得到一个浅克隆,其中每个字段都被复制。
所以如果你开始:
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 -> ... // 新分配
要进行深度克隆,您需要编写自己的克隆例程,克隆每个级别。
这很容易出错。最好养成使用不可变对象的习惯,其中浅拷贝 "just work"——因为您永远无法更改字段,所以您不会意外影响与您正在工作的对象共享同一对象的对象与.
我注意到您使用的示例与此 link 相同:A Guide to Object Cloning in Java
可能您已经知道 super.clone() 的默认实现执行浅拷贝而不是深拷贝。
在这种情况下,两个对象实例:emp1 和 emp2 将包含指向内存中相同位置的引用。
引用 Department 实例,在克隆 emp1 对象后,它在内存中的引用是这样的:
通过设置:
emp1.setDept(dept2);
它将 emp1 的引用更改为新对象 dept2:
在这种情况下,您会看到它是深拷贝,但实际上不是。只是 emp1 更改了它对内存中新位置的引用。
在另一种情况下,当您设置:
emp1.getDept().setdName("Testing");
您将 Departman 的姓名更改为 "Testing" - 这将对两个对象实例可见。
如果您还有其他问题,请告诉我。