12

在 Effective Java 中,作者指出:

如果一个类实现了 Cloneable,则 Object 的 clone 方法返回该对象的逐个字段副本;否则会抛出 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()是算法规范(用简单的语言),那就太好了。

4

4 回答 4

5

是的,逐个字段复制确实意味着当它创建新(克隆)对象时,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的克隆。相反,我通常创建自己的“复制”方法。

于 2010-05-23T04:22:27.467 回答
4

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

于 2010-05-23T01:01:16.163 回答
4
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;
于 2010-05-23T01:38:59.460 回答
1

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

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

于 2010-05-23T06:15:54.607 回答