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

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

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()方法?

克隆方法旨在进行深层复制。 确保您了解深拷贝和浅拷贝之间的区别。 在您的情况下,复制构造函数可能是您想要的模式。 在某些情况下,您无法使用此模式,例如,因为您正在为类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()也适用于任何子类(克隆模式的弱点;如果可能的话,使类为final clone()时,不要包含throws子句
  • 保留不可变和原始字段,但在调用super.clone()之后手动克隆可变对象字段(克隆模式的另一个弱点,因为这些字段不能成为最终字段)

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

如果您希望能够在实例上调用clone() ,则实现Cloneable接口并将该方法设为public。 如果您不希望能够在实例上调用它,但您确实希望确保子类可以调用它们的super.clone()并获得它们所需的内容,那么不要实现Cloneable并保持方法protected超类还没有宣布它是公开的。

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

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类是子类,这也会起作用,而您的clone实现将始终创建Person的实例(例如,不是Employee的Employee实例)。

Object类中的clone()执行内存的浅表副本,而不是像构造函数那样调用方法。 为了在任何不实现clone()本身的对象上调用clone() ,您需要实现Clonable接口。

如果重写clone()方法,则不必实现该接口。

正如JavaDoc所说,这将导致exception:

 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版本)将导致引用完全相同的 StringBuilder的新A实例,即对克隆实例中的sb更改将导致原始A sb更改实例。

这意味着使用浅拷贝,这是为什么覆盖clone()通常更好的一个原因。

编辑:正如旁注,使用返回类型协方差将使您的重写clone()更明确:

 public Person clone() { ... } 

克里斯并不成熟,约书亚布洛赫说:

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

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

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

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

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

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

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

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