【问题标题】: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() 的算法规范是什么(用简单的语言),那就太好了。

【问题讨论】:

    标签: java clone cloneable


    【解决方案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、枚举(如果没有被错误地设置为可变)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-14
        • 2015-01-29
        • 1970-01-01
        • 2011-02-18
        • 2013-06-09
        • 2018-08-11
        • 1970-01-01
        • 2016-11-01
        相关资源
        最近更新 更多