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

时间:2011-09-28 07:49:17

标签: java clone

我不了解克隆自定义对象的机制。例如:

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的提议以及为什么在Object类中定义了clone()方法?

8 个答案:

答案 0 :(得分:12)

克隆方法旨在制作深层副本。确保您了解深拷贝和浅拷贝之间的区别。在您的情况下,复制构造函数可能是您想要的模式。在某些情况下,您无法使用此模式,例如,因为您正在为类X创建子类,并且您无法访问所需的X构造函数。如果X正确覆盖其克隆方法(如果需要),那么您可以按以下方式复制:

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子句。
  • 单独保留不可变和原始字段,但在调用super.clone()后手动克隆可变对象字段(克隆模式的另一个弱点,因为这些字段不能成为最终字段)

clone()的{​​{1}}方法(当所有超类都遵守契约时最终将被调用)生成浅层副本并处理新对象的正确运行时类型。请注意在整个过程中如何调用构造函数。

如果您希望能够在实例上调用Object,请实现clone()接口并将该方法设为公共。如果您不希望能够在实例上调用它,但您确实希望确保子类可以调用其Cloneable并获得所需内容,那么请不要实现super.clone()并保留方法Cloneable,如果你的超类已经公开了它。

克隆模式很难并且存在很多陷阱。确保它是你需要的。考虑复制构造函数或静态工厂方法。

答案 1 :(得分:8)

clone()中的Object执行内存的浅层副本,而不是像构造函数那样调用方法。要在任何未实现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的更改将导致原始sb实例中A的更改。

这意味着有一个浅层副本,这就是覆盖clone()通常更好的一个原因。

编辑:就像旁注一样,使用返回类型协方差会使你的被覆盖的clone()更明确:

public Person clone() {
  ...
}

答案 2 :(得分:8)

JVM能够为您克隆对象,因此您不应该自己构建新人。只需使用此代码:

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的Employee实例)。

答案 3 :(得分:1)

无需在clone()方法中明确创建对象。只需调用super.clone()即可创建此对象的副本。它将执行浅层克隆。

答案 4 :(得分:1)

正如Joshua bloch所说,克隆并不成熟:

http://www.artima.com/intv/bloch13.html

答案 5 :(得分:0)

它与任何此类界面的目的相同。它主要允许方法(等)接受任何Clonable对象,并且可以访问他们需要的方法,而不必将自己限制在一个特定的对象上。不可否认,Clonable可能是这方面的 less 有用接口之一,但肯定有你想要它的地方。如果你想要更多的想法,可以考虑接口Comparable,例如允许你有排序列表(因为列表不需要知道对象是什么,只有它们可以被比较)。

答案 6 :(得分:0)

如果你没有声明克隆接口,那么在调用clone方法时你应该得到CloneNotSupportException。如果你声明然后调用clone方法,它将进行浅拷贝。

答案 7 :(得分:0)

在您的示例中,您没有进行实际克隆。您将覆盖对象类的clone()方法并给出您自己的实现。但是在你的克隆方法中,你正在创建一个新的Person对象。并返回它。在这种情况下,实际对象不会被克隆。

所以你的克隆方法应该是:

public Object clone() {
      return super.clone();
  }

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