在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
类的字段副本,我会说我不需要明确复制字段x
和{{1}因为上面显示的代码足以构成y
类的克隆。也就是说,下面的代码是多余的:
Point
我是对的吗?
我知道克隆对象的引用会自动指向原始对象的引用指向的位置,我只是不确定具体的值类型会发生什么。如果有人能够清楚地说明@Override
public Point clone() {
Point newObj = (Point)super.clone();
newObj.x = this.x; //redundant
newObj.y = this.y; //redundant
}
的算法规范是什么(用简单的语言)那就太棒了。
答案 0 :(得分:5)
是的,字段副本的字段意味着当它创建新的(克隆的)对象时,JVM会将原始对象中每个字段的值复制到克隆的对象中。不幸的是,这确实意味着你有一个浅的副本。如果您需要深层复制,可以覆盖克隆方法。
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的克隆。相反,我通常会创建自己的“复制”方法。
答案 1 :(得分:4)
这意味着一个浅层副本 - 字段被复制,但是如果你有任何引用,这些指向的内容不会被复制 - 你将有两个对同一个对象的引用,一个在旧对象中,一个在新的,克隆的对象。但是,对于具有基本类型的字段,该字段是数据本身,因此无论如何都会复制它们。
答案 2 :(得分: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;
答案 3 :(得分:1)
默认克隆执行值的浅表副本。对于原始值,这已足够,无需额外的工作。
对于对象,浅拷贝意味着仅复制参考。因此,在这些情况下,通常需要深层复制。例外情况是引用指向不可变对象。不可变对象不能改变其明显状态,因此可以安全地复制它们的引用。例如,这适用于String,Integer,Float,枚举(如果没有错误地变为可变)。