如何使用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()
也适用于任何子类(克隆模式的弱点;如果可能的话,使类为finalclone()
时,不要包含throws子句 - 保留不可变和原始字段,但在调用
super.clone()
之后手动克隆可变对象字段(克隆模式的另一个弱点,因为这些字段不能成为最终字段)
Object
的clone()
方法(最终将在所有超类遵循契约时调用)生成浅层副本并处理新对象的正确运行时类型。 请注意在整个过程中如何调用构造函数。
如果您希望能够在实例上调用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() { ... }
克里斯并不成熟,约书亚布洛赫说:
它与任何此类界面的目的相同。 它主要允许方法(等)接受任何Clonable对象,并且可以访问他们需要的方法,而不必将自己限制在一个特定的对象上。 诚然,Clonable可能是这方面不太有用的界面之一,但肯定有你可能需要它的地方。 如果你想要更多的想法,可以考虑接口Comparable,例如允许你有排序列表(因为列表不需要知道对象是什么,只有它们可以被比较)。
如果你没有声明可克隆接口,那么在调用clone方法时你应该得到CloneNotSupportException。如果你声明然后调用clone方法,它将进行浅拷贝。
在您的示例中,您没有进行实际克隆。您将覆盖对象类的clone()方法并给出您自己的实现。 但是在你的克隆方法中,你正在创建一个新的Person对象。 并返回它。所以在这种情况下,实际对象不会被克隆。
所以你的克隆方法应该是这样的:
public Object clone() { return super.clone(); }
所以这里克隆将由超类方法处理。
这里没有必要在clone()
方法中显式创建对象。 只需调用super.clone()
创建此对象的副本。 它将执行浅层克隆。