Java对象克隆中的浅拷贝与深拷贝

时间:2020-01-09 10:34:48  来源:igfitidea点击:

在这篇文章中,我们将看到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方法。