Object.clone() 的逐个字段复制是啥?

Posted

技术标签:

【中文标题】Object.clone() 的逐个字段复制是啥?【英文标题】:What is this field-by-field copy done by Object.clone()?Object.clone() 的逐个字段复制是什么? 【发布时间】:2011-02-22 20:05:42 【问题描述】:

在 Effective Java 中,作者指出:

如果一个类实现了 Cloneable, 对象的克隆方法返回一个 对象的逐个字段副本; 否则它会抛出 CloneNotSupportedException。

我想知道的是他对逐字段复制的含义。这是否意味着如果该类在内存中有 X 个字节,它只会复制那块内存?如果是,那么我可以假设原始类的所有值类型都将被复制到新对象中吗?

class Point implements Cloneable
    private int x;
    private int y;

    @Override
    public Point clone() 
        return (Point)super.clone();
    

如果Object.clone() 所做的是逐个字段复制Point 类,我会说我不需要显式复制字段xy,即上面显示的代码足以克隆Point 类。也就是说,下面这段代码是多余的:

@Override
public Point clone() 
    Point newObj = (Point)super.clone();
    newObj.x = this.x; //redundant
    newObj.y = this.y; //redundant

我说的对吗?

我知道克隆对象的引用会自动指向原始对象的引用指向的位置,我只是不确定值类型具体会发生什么。如果有人能清楚地说明Object.clone() 的算法规范是什么(用简单的语言),那就太好了。

【问题讨论】:

【参考方案1】:

是的,逐个字段复制确实意味着当它创建新的(克隆的)对象时,JVM 会将每个字段的值从原始对象复制到克隆对象中。不幸的是,这确实意味着你有一个浅拷贝。如果你想要一个深拷贝,你可以重写 clone 方法。

class Line implements Cloneable 

    private Point start;
    private Point end;

    public Line() 
        //Careful: This will not happen for the cloned object
        SomeGlobalRegistry.register(this);
    

    @Override
    public Line clone() 
        //calling super.clone is going to create a shallow copy.
        //If we want a deep copy, we must clone or instantiate
        //the fields ourselves
        Line line = (Line)super.clone();
        //assuming Point is cloneable. Otherwise we will
        //have to instantiate and populate it's fields manually
        line.start = this.start.clone();
        line.end = this.end.clone;
        return line;
    

关于克隆的另一件重要的事情是,克隆对象的构造函数永远不会被调用(只复制字段)。因此,如果构造函数初始化了一个外部对象,或者将该对象注册到某个注册表中,那么克隆对象就不会发生这种情况。

我个人更喜欢不使用 Java 的克隆。相反,我通常创建自己的“复制”方法。

【讨论】:

为什么不用Java的克隆,自己写方法呢?有什么具体原因吗? 阅读有效的 Java。有很多。 @zengr 因为 clone() 不会创建深拷贝,所以我必须自己实现 clone 方法的主体,还要承担 Cloneable 和 CloneNotSupportedException 的负担。除此之外,还有一些其他问题,为什么我选择创建自己的方法。 《Effective Java》中对它们进行了很好的解释 原语为我们完成了吗?还是我们也需要显式复制它们?【参考方案2】:

这意味着一个浅拷贝——字段被复制,但如果你有任何引用,这些指向的内容不会被复制——你将有两个对同一个对象的引用,一个在旧对象中,一个在旧对象中新的,克隆的对象。但是,对于具有原始类型的字段,该字段就是数据本身,因此无论如何都会被复制。

【讨论】:

正确,但如果 clone 方法没有像 super.clone() 那样实现,它可能不会实现到合约中。 啊,是的,这当然是真的。我注意到在示例中他扩展了 Cloneable,这似乎不正确——您将实现它然后覆盖 Cloneable 方法(也许扩展 Cloneable 是它在 Java 1 中的工作方式?)。我想实现这个是说“嘿,我有这种特殊的可克隆性质,我正确克隆并在我的克隆方法中进行手动深度复制”。但是,除非您覆盖,否则正如您所说,来自对象的 clone() 会执行浅拷贝。【参考方案3】:
newObj.x = this.x; //redundant
newObj.y = this.y; //redundant

没错——这些都是多余的,因为它们已经被 Object 的 clone() 方法复制了。

将其视为数据副本是正确的。原始类型被复制,引用也被复制,因此它们指向同一个对象。例如,

class A implements Cloneable 
  Object someObject;


A a = new A();
a.someObject = new Object();

A cloneA = (A)a.clone();
assert a.someObject==cloneA.someObject;

【讨论】:

【参考方案4】:

默认克隆执行值的浅拷贝。对于原始值,这就足够了,不需要额外的工作。

对于对象,浅拷贝意味着只拷贝引用。因此,在这些情况下,通常需要深拷贝。例外情况是引用指向不可变对象时。不可变对象不能改变其明显的状态,因此可以安全地复制它们的引用。例如,这适用于 String、Integer、Float、枚举(如果没有被错误地设置为可变)。

【讨论】:

以上是关于Object.clone() 的逐个字段复制是啥?的主要内容,如果未能解决你的问题,请参考以下文章

请问java中深度copy一个二维数组是啥意思?怎么用代码实现?

JavaScript中的对象复制(Object Clone)

ruby中的复制 dup clone

java对象的内存布局

Java克隆(Clone)的应用

在java中,如何复制一个对象?比如说string对象