如何使用clone()方法克隆一个Java对象

21

我不理解如何复制自定义对象的机制。例如:

public class Main{

    public static void main(String [] args) {

        Person person = new Person();
        person.setFname("Bill");
        person.setLname("Hook");

        Person cloned = (Person)person.clone();
        System.out.println(cloned.getFname() + " " + cloned.getLname());
    }
}

class Person implements Cloneable{

    private String fname;
    private String lname;

    public Object clone() {

        Person person = new Person();
        person.setFname(this.fname);
        person.setLname(this.lname);
        return person;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public void setLname(String lname){
        this.lname = lname;
    }

    public String getFname(){
        return fname;
    }

    public String getLname() {
        return lname;
    }
}

这个示例展示了正确的克隆方式,就像书中所写的一样。但是我可以删除类名定义中的implements Cloneable并获得相同的结果。

因此,我不理解为什么需要实现Cloneable以及为什么将clone()方法定义在Object类中?


请查看javadoc文档:http://download.oracle.com/javase/6/docs/api/java/lang/Cloneable.html - Paul Bellora
https://dev59.com/J3I95IYBdhLWcg3wyBGc#2156367 - Bozho
https://dev59.com/Ak7Sa4cB1Zd3GeqP7N2L#3180729 - Bozho
从javadoc中:在一个没有实现Cloneable接口的实例上调用Object的clone方法会导致抛出CloneNotSupportedException异常。但是,如果我只是覆盖了Object类中的这个方法,就不会有任何异常吗? - Dima Zelinskyi
@Dmytro Zelinskyi:由于克隆方法应该以调用super.clone()开始,因此可能会出现超类明确抛出CloneNotSupportedException的情况。请参见我的答案。 - Rinke
8个回答

20

clone方法的目的是制作深度复制。确保您了解深层复制和浅层复制之间的区别。在您的情况下,复制构造函数可能是您想要的模式。但在某些情况下,您无法使用此模式,例如因为您正在对类X进行子类化,并且您无法访问所需的X构造函数。如果X正确覆盖其clone方法(如有必要),则可以按以下方式进行复制:

class Y extends X implements Cloneable {

    private SomeType field;    // a field that needs copying in order to get a deep copy of a Y object

    ...

    @Override
    public Y clone() {
        final Y clone;
        try {
            clone = (Y) super.clone();
        }
        catch (CloneNotSupportedException ex) {
            throw new RuntimeException("superclass messed up", ex);
        }
        clone.field = this.field.clone();
        return clone;
    }

}

当覆盖你的克隆方法时,通常应该:

  • 将返回类型更具体化
  • 从调用super.clone()开始
  • 如果你知道clone()对于任何子类也可以使用,则不要包括throws子句(克隆模式的弱点; 如果可能,使类final)
  • 保留不可变和原始字段,但在调用super.clone()后手动克隆可变对象字段(克隆模式的另一个弱点,因为这些字段无法被设定为final)

Objectclone()方法(当所有超类都遵守合同时最终被调用),进行浅拷贝并处理新对象的正确运行时类型。注意整个过程中没有调用构造函数。

如果你想能够在实例上调用clone(),则实现Cloneable接口并将方法设置为public。如果你不想在实例上调用它,但是希望确保子类可以调用其super.clone()并获得所需内容,则不要实现Cloneable,并将方法设置为protected(如果你的超类尚未将其声明为public)。

克隆模式很困难,并有很多陷阱。请确保它是你需要的。考虑使用复制构造函数或静态工厂方法。


9
在类Object中,clone()方法执行的是浅拷贝而不是调用构造函数等方法。如果要在任何未实现clone()方法的对象上调用clone(),则需要实现Clonable接口。
如果重写了clone()方法,则无需实现该接口。
正如JavaDoc所说,这将导致异常:
class A {
  private StringBuilder sb; //just some arbitrary member
}

...

new A().clone(); //this will result in an exception, since A does neither implement Clonable nor override clone()

如果在该示例中,A 实现了 Clonable 接口并调用 clone()(即 Object 版本),那么结果将是一个新的 A 实例,引用了完全相同的 StringBuilder,也就是说,在克隆实例中对 sb 的更改将导致原始 A 实例中的 sb 更改。这就是所谓的浅拷贝,这也是通常最好覆盖 clone() 的原因之一。
编辑:只是作为旁注,使用返回类型协变会使您重写的 clone() 更加明确。
public Person clone() {
  ...
}

Thomas,如果我在A中不实现Cloneable并且不重写clone(),那么我会收到编译错误,因为clone()是受保护的。 - Dima Zelinskyi
@Dmytro 是的,这个例子只是为了说明一下。即使使用反射调用 clone() 或在你重写的版本中调用 super.clone(),你仍然会在缺少 Clonable 接口时得到异常。 - Thomas

9

JVM可以为您克隆对象,因此您不需要自己构造新的person。只需使用以下代码:

class Person implements Cloneable {
    // ...
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

或者

class Person implements Cloneable {
    // ...
    @Override
    public Object clone() {
        try {
            return super.clone();
        }
        catch (CloneNotSupportedException e) {
            throw new Error("Something impossible just happened");
        }
    }
}

即使Person类被子类化,这种方法也能正常工作,而你的克隆实现总是会创建一个Person实例(而不是一个Employee实例,例如)。

1

1

clone()方法中,没有必要显式创建对象。只需调用super.clone()即可创建此对象的副本。它将执行浅克隆。


0
在你的例子中,你并没有进行实际的克隆。你重写了Object类的clone()方法,并给出了自己的实现。但是在你的克隆方法中,你创建了一个新的Person对象并返回它。因此,在这种情况下,实际的对象并没有被克隆。
所以你的克隆方法应该像这样:
public Object clone() {
      return super.clone();
  }

所以这里克隆将由超类方法处理。


0

它与任何此类接口的目的相同。主要是允许方法(等等)接受任何可克隆对象,并访问他们需要的方法,而不限制于一个特定的对象。诚然,在这方面,Clonable 可能是其中一个较少有用的接口,但肯定有一些场合你可能需要它。如果您想了解更多,请考虑 Comparable 接口,例如允许您拥有排序列表(因为列表不需要知道对象是什么,只需要知道它们可以进行比较)。


0
如果您没有声明可克隆接口,那么在调用克隆方法时应该会收到CloneNotSupportException异常。如果您声明了并调用了克隆方法,则会进行浅拷贝。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接