在Java中使用clone()方法进行对象克隆

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

Java中的对象克隆是创建对象副本的过程。要在Java中克隆对象,请使用Object类的clone()方法。

clone()方法

clone()方法在Object类中定义为受保护的方法。

protected Object clone() throws CloneNotSupportedException

如果要在类中编写clone()方法以覆盖Object类中的那个方法,则必须将其作为公共方法编写。

public Object clone() throws CloneNotSupportedException

使用clone()方法在Java中克隆对象

克隆对象的要求之一是要克隆其对象的类必须实现Cloneable接口。可克隆的接口是标记接口,没有定义其自身的成员。如果我们尝试在未实现Cloneable接口的类上调用clone()方法,则会引发CloneNotSupportedException。

如果我们必须使用clone()方法创建对象obj的副本,则可以编写

obj.clone();

clone()方法的默认实现创建与原始对象相同类的对象。克隆对象的成员变量被初始化为具有与原始对象的相应成员变量相同的值。

Java对象克隆的示例

这是通过使用super直接调用Object类的clone()方法来克隆对象的简单示例。

class TestClone implements Cloneable{
  int i;
  String str;
  TestClone(int i, String str){
    this.i = i;
    this.str = str;
  }
  TestClone cloneObject() throws CloneNotSupportedException{
    // calling Object class clone method
    return (TestClone) super.clone();
  }
}

public class CloningDemo {
  public static void main(String[] args) {
    TestClone obj1 = new TestClone(10, "Clone Test");
    try {
      TestClone obj2 = obj1.cloneObject();
      System.out.println("Original object- " + obj1.i + " " + obj1.str);
      System.out.println("Cloned object- " + obj2.i + " " + obj2.str);
      // Checking object references for objects
      if(obj1 == obj2){
        System.out.println("Object references are same");
      }else{
        System.out.println("Object references are not same");
      }
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 			
  }
}

输出:

Original object- 10 Clone Test
Cloned object- 10 Clone Test
Object references are not same

在代码中,我们可以注意到对象克隆的所有操作

  • 要克隆其对象的类实现Cloneable接口。

  • 调用super.clone()以使用Object类中clone()方法的默认实现。

  • 我们可以看到一个对象的副本与原始对象具有相同的状态(成员变量的值与原始对象相同)。

  • 尽管状态相同,但引用并不相同。克隆的对象是不同的。

覆盖类中的克隆方法
我们也可以在类中重写clone方法,尽管它不一定总是必需的。

class TestClone implements Cloneable{
  int i;
  String str;
  TestClone(int i, String str){
    this.i = i;
    this.str = str;
  }
  // overriding clone method
  public Object clone() throws CloneNotSupportedException{
    return super.clone();
  }
}

public class CloningDemo {
  public static void main(String[] args) {
    TestClone obj1 = new TestClone(10, "Clone Test");
    try {
      TestClone obj2 = (TestClone) obj1.clone();
      System.out.println("Original object- " + obj1.i + " " + obj1.str);
      System.out.println("Cloned object- " + obj2.i + " " + obj2.str);
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 			
  }
}

输出:

Original object- 10 Clone Test
Cloned object- 10 Clone Test

对象克隆的优点

  • Java中的对象克隆是获取带有状态的新对象的最简单方法。我们无需完成调用new运算符来创建对象并为其字段分配值的整个过程。

  • 克隆对象是具有其自身状态的不同对象。更改一个对象不会更改另一个对象的状态。

对象克隆的缺点

  • 尽管使用默认的clone()方法可以使对象的克隆过程自动进行,但同时我们无法控制整个过程。克隆对象时,甚至不会调用构造函数。

  • Object.clone()方法创建一个浅表副本。如果对象中的字段是原始字段或者不可变字段,那就很好。如果原始对象持有对另一个对象的引用,则该引用在原始对象和克隆对象之间共享,这可能是一个问题。

对象克隆–浅拷贝

克隆对象时,会进行一些明智的复制,将原始对象中每个字段的值都复制到克隆对象的相应字段中。 Java中这种对象克隆的方式称为"浅拷贝"。正如我们在示例中已经看到的那样,这对于原始值很好用。

但是,当一个对象持有对另一个对象的引用时,创建精确副本的过程可能会出现问题,因为该引用将被复制到克隆对象中,并且两个对象共享该对象引用。那时候我们可能需要一个深层副本。

Java对象克隆–深层复制

如果我们不希望在对象克隆过程中照原样复制任何对象引用,则需要进行深层复制。
为了创建深层副本,我们需要显式重写类中的clone()方法,并为原始对象引用的对象也调用clone()方法。
创建深度副本的成本更高,因为我们需要为所有引用的对象创建新对象。这也更加复杂,因为可能需要克隆整个对象树。

要了解有关Java对象克隆中的浅拷贝和深拷贝的更多信息,请参阅这篇文章。

Java中的对象克隆–深度复制示例

在该示例中,有两个类一和二。在第二类中,引用了一个第一类的对象。为了进行深层复制,在类2中重写了clone()方法,并且也显式调用了被引用对象(类One的对象)的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);
      objCopy.getObj().setStr("Value changed");
      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- 5 Value changed

从输出中可以看到,在类中修改了str字段时,由于深拷贝,克隆对象引用的One对象的更改未反映在原始对象中。