Java对象克隆中的浅拷贝与深拷贝
在这篇文章中,我们将看到Java对象克隆中浅拷贝和深拷贝之间的区别。要了解差异,在克隆对象时了解浅拷贝和深拷贝的概念非常重要,因此,我们首先尝试使用浅拷贝和深拷贝的示例来了解概念。
Java对象克隆中的浅表复制
使用clone()方法克隆对象时,会发生按位复制,其中原始对象中每个字段的值都将复制到克隆对象的相应字段中。使用默认的clone()方法在Java中进行这种对象克隆的方式将创建一个Shallow副本。
这被称为浅表副本,因为此创建精确副本的过程适用于原始值,但当原始对象持有对另一个对象的引用时,原始对象与克隆对象之间具有共享状态。
在这种情况下,引用将被复制到克隆对象中,并且两个对象共享该对象引用。
例如,如果有一个MyClass类,它具有另一个对象objA作为字段。
class MyClass implements Cloneable{ private ClassA objA; ... ... }
如果存在MyClass类的对象myObj,并且该对象被克隆,则objA引用在myObj和myObj的克隆副本之间共享。
浅拷贝
Java示例中的浅表复制
在该示例中,有两个类一和二。在第二类中,引用了一个第一类的对象。
class One implements Cloneable{ int i; String str; One(int i, String str){ this.i = i; this.str = str; } // overriding clone method public Object clone() throws CloneNotSupportedException{ return super.clone(); } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } class Two implements Cloneable{ int j; // Object reference One obj; Two(int j, One obj){ this.j = j; this.obj = obj; } public Object clone() throws CloneNotSupportedException{ Two objCloned = (Two) super.clone(); return objCloned; } public int getJ() { return j; } public void setJ(int j) { this.j = j; } public One getObj() { return obj; } public void setObj(One obj) { this.obj = obj; } } public class CloningDemo { public static void main(String[] args) { One one = new One(10, "Clone Test"); Two two = new Two(5, one); try { Two objCopy = (Two) two.clone(); System.out.println("Original object- " + two.getJ() + " " + two.getObj().str); System.out.println("Cloned object- " + + objCopy.getJ() + " " + objCopy.getObj().str); // modifying field in the referenced object objCopy.getObj().setStr("New Value"); // Modifying primtive value objCopy.setJ(25); System.out.println("---After changing value---"); System.out.println("Original object- " + two.getJ() + " " + two.getObj().str); System.out.println("Cloned object- " + + objCopy.getJ() + " " + objCopy.getObj().str); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出:
Original object- 5 Clone Test Cloned object- 5 Clone Test ---After changing value-- Original object- 5 New Value Cloned object- 25 New Value
从输出中可以看到,当副本中引用对象的字段发生更改时,更改也将反映在原始对象中。复制中完成的原始类型字段更改(j的值更改为25)未反映在原始对象中。
Java对象克隆中的深层复制
为了避免原始对象和克隆对象之间的这种状态共享,在这种情况下,两个对象都反映了引用对象的变异,因此需要深度复制。对于在Java中克隆对象时进行深度复制的情况,即使被引用的对象也是单独创建的,因此两个对象都有各自独立的引用。
例如,如果有一个MyClass类,它具有另一个对象objA作为字段。
class MyClass implements Cloneable{ private ClassA objA; ... ... }
如果有一个MyClass类的对象myObj并创建了一个深拷贝,那么即使是objA的独立副本也存在于myObj和myObj的克隆副本中。
深拷贝
Java深层复制示例
进行深层复制时,有两种情况,下面将通过示例讨论这两种情况:
1如果引用其对象的类实现了Cloneable接口,那么我们也可以显式调用该对象的clone()方法。
在示例中,我们可以看到在类Two中重写了clone方法,并且也显式调用了所引用对象的clone()方法,以创建该对象的不同副本。
class One implements Cloneable{ int i; String str; One(int i, String str){ this.i = i; this.str = str; } // overriding clone method public Object clone() throws CloneNotSupportedException{ return super.clone(); } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } class Two implements Cloneable{ int j; // Object reference One obj; Two(int j, One obj){ this.j = j; this.obj = obj; } public Object clone() throws CloneNotSupportedException{ Two objCloned = (Two) super.clone(); // Explicitly calling clone method for // object of Class One objCloned.obj = (One) obj.clone(); return objCloned; } public int getJ() { return j; } public void setJ(int j) { this.j = j; } public One getObj() { return obj; } public void setObj(One obj) { this.obj = obj; } } public class CloningDemo { public static void main(String[] args) { One one = new One(10, "Clone Test"); Two two = new Two(5, one); try { Two objCopy = (Two) two.clone(); System.out.println("Original object- " + two.getJ() + " " + two.getObj().str); System.out.println("Cloned object- " + + objCopy.getJ() + " " + objCopy.getObj().str); // modifying field in the referenced object objCopy.getObj().setStr("New Value"); // Modifying primtive value objCopy.setJ(25); System.out.println("---After changing value---"); System.out.println("Original object- " + two.getJ() + " " + two.getObj().str); System.out.println("Cloned object- " + + objCopy.getJ() + " " + objCopy.getObj().str); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出:
Original object- 5 Clone Test Cloned object- 5 Clone Test ---After changing value-- Original object- 5 Clone Test Cloned object- 25 New Value
如我们所见,当副本中引用对象中的字段发生更改时,完成深层复制后更改不会反映在原始对象中。
2如果引用其对象的类未实现Cloneable接口,则如果在其上调用clone()方法,则会引发CloneNotSupportedException。在这种情况下,我们需要创建一个新对象,并在clone()方法中为字段显式分配值。
class One{ int i; String str; One(int i, String str){ this.i = i; this.str = str; } // overriding clone method public Object clone() throws CloneNotSupportedException{ return super.clone(); } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } class Two implements Cloneable{ int j; // Object reference One obj; Two(int j, One obj){ this.j = j; this.obj = obj; } public Object clone() throws CloneNotSupportedException{ Two objCloned = (Two) super.clone(); // Creating new object One newObj = new One(12, "New Value"); // assigning new oject to the clone objCloned.setObj(newObj); return objCloned; } public int getJ() { return j; } public void setJ(int j) { this.j = j; } public One getObj() { return obj; } public void setObj(One obj) { this.obj = obj; } } public class CloningDemo { public static void main(String[] args) { One one = new One(10, "Clone Test"); Two two = new Two(5, one); try { Two objCopy = (Two) two.clone(); System.out.println("Original object- " + two.getJ() + " " + two.getObj().getI() + " " + two.getObj().getStr()); System.out.println("Cloned object- " + + objCopy.getJ() + " " + objCopy.getObj().getI() + " " + objCopy.getObj().getStr()); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出:
Original object- 5 10 Clone Test Cloned object- 5 12 New Value
Java中的浅拷贝与深拷贝
浅拷贝便宜且易于实现。深拷贝非常昂贵,因为每个引用的对象都必须单独创建。它也更加复杂,因为对象树可能很长。
在浅表复制的情况下,尽管使用其自己的字段集创建了对象的不同副本,但是对象引用是共享的。在深度复制的情况下,即使对于引用的对象,也会创建单独的副本。
默认情况下,对象类的clone方法创建浅表副本。为了创建深层副本,我们需要重写clone方法,并在引用的对象上也调用clone方法。