深拷贝,浅拷贝,克隆

时间:2011-05-31 02:54:43

标签: java clone

我需要澄清深度拷贝,浅拷贝和Java中的克隆之间的区别

4 个答案:

答案 0 :(得分:109)

不幸的是,“浅拷贝”,“深拷贝”和“克隆”都是相当不明确的术语。


在Java上下文中,我们首先需要区分“复制值”和“复制对象”。

int a = 1;
int b = a;     // copying a value
int[] s = new int[]{42};
int[] t = s;   // copying a value (the object reference for the array above)

StringBuffer sb = new StringBuffer("Hi mom");
               // copying an object.
StringBuffer sb2 = new StringBuffer(sb);

简而言之,对类型为引用类型的变量的引用赋值是“复制值”,其中值是对象引用。要复制对象,需要明确地或在引擎盖下使用new


现在进行“浅”与“深”复制对象。浅拷贝通常意味着只复制对象的一个​​级别,而深度复制通常意味着复制多个级别。问题在于决定一个级别的含义。考虑一下:

public class Example {
    public int foo;
    public int[] bar;
    public Example() { };
    public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; };
}

Example eg1 = new Example(1, new int[]{1, 2});
Example eg2 = ... 

正常的解释是eg1的“浅”副本将是一个新的Example对象,其foo等于1且其bar字段指的是同一个数组和原来一样; e.g。

Example eg2 = new Example(eg1.foo, eg1.bar);

eg1的“深层”副本的正常解释将是Example等于1的新foo对象,其bar字段指的是 原始数组的副本; e.g。

Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));

(来自C / C ++背景的人可能说引用赋值会产生浅拷贝。但是,这不是我们通常在Java上下文中浅层复制的意思......)< / p>

还存在两个不确定的问题/领域:

  • 深度有多深?它停在两个级别吗?三个级别?它是指连接对象的整个图形吗?

  • 封装数据类型怎么样;例如一个字符串? String实际上不只是一个对象。实际上,它是一个带有一些标量字段的“对象”,以及对一个字符数组的引用。但是,API完全隐藏了字符数组。因此,当我们谈论复制字符串时,将其称为“浅”副本还是“深度”副本是否有意义?或者我们应该把它称为副本?


最后,克隆。克隆是一种存在于所有类(和数组)上的方法,通常被认为是生成目标对象的副本。但是:

  • 这种方法的规范故意没有说明这是浅层还是深层(假设这是一个有意义的区别)。

  • 事实上,该规范甚至没有明确规定克隆产生新对象。

以下是the javadoc所说的内容:

  

“创建并返回此对象的副本。”copy“的确切含义可能取决于对象的类。一般意图是,对于任何对象x,表达式x.clone() != x将是真的,并且表达式x.clone().getClass() == x.getClass()将是真的,但这些并非绝对要求。虽然通常情况x.clone().equals(x)将是真的,但这不是绝对要求。“

请注意,这就是说克隆可能在某个极端是目标对象,而在另一个极端,克隆可能与原始相等。这假设甚至支持克隆。

简而言之,克隆可能意味着每个Java类都有不同的东西。


有些人认为(正如@supercat在评论中所做的那样)Java clone()方法被破坏了。但我认为正确的结论是克隆的概念在OO的背景下被打破了。在AFAIK中,不可能开发出一致且可在所有对象类型中使用的统一克隆模型。

答案 1 :(得分:22)

术语“clone”是不明确的(尽管Java类库包含Cloneable接口),并且可以引用深层副本或浅层副本。深层/浅层副本不是专门与Java相关联的,而是与制作对象副本有关的一般概念,并且指的是如何复制对象的成员。

举个例子,假设你有一个人类:

class Person {
    String name;
    List<String> emailAddresses
}

如何克隆此类的对象?如果要执行浅表副本,则可以复制名称并在新对象中引用emailAddresses。但是如果你修改了emailAddresses列表的内容,你就会修改两个副本中的列表(因为这就是对象引用的工作方式)。

深层复制意味着您递归复制每个成员,因此您需要为新List创建新的Person,然后将内容从旧对象复制到新对象。

虽然上面的例子很简单,但深拷贝和浅拷贝之间的差异很大,并且对任何应用程序都有重大影响,特别是如果你试图提前设计一个通用的克隆方法,而不知道某个人以后会如何使用它。有些时候你需要深层或浅层语义,或者某些混合,你深深地复制一些成员而不是其他成员。

答案 2 :(得分:18)

  • 深层复制:克隆此对象以及对其所有其他对象的每个引用
  • 浅拷贝:克隆此对象并保留其引用
  • Object clone()抛出CloneNotSupportedException:未指定是应该返回深拷贝还是浅拷贝,但至少是:o.clone()!= o

答案 3 :(得分:2)

“浅拷贝”和“深拷贝”这两个词有点模糊;我建议使用术语“成员克隆”和我称之为“语义克隆”的术语。对象的“成员克隆”是与原始对象具有相同运行时类型的新对象,对于每个字段,系统有效地执行“newObject.field = oldObject.field”。基础Object.Clone()执行成员克隆;成员克隆通常是用于克隆对象的正确起点,但在大多数情况下,在成员克隆之后将需要一些“修复工作”。在许多情况下,尝试使用通过成员克隆生成的对象而不首先执行必要的修复将导致不良事件发生,包括克隆的对象的损坏以及可能还有其他对象。有些人使用“浅层克隆”这个术语来指代成员克隆,但这并不是该术语的唯一用途。

“语义克隆”是一个对象,它包含与原始相同的数据,从类型的角度来看。为了检查,考虑一个包含数组&gt;的BigList。和一个计数。这种对象的语义级克隆将执行成员克隆,然后替换Array&gt;使用新数组,创建新的嵌套数组,并将所有T从原始数组复制到新数组。 它不会尝试对T本身进行任何深度克隆。具有讽刺意味的是,有些人将克隆称为“浅层克隆”,而另一些人称之为“深度克隆”。不完全有用的术语。

虽然有些情况下真正的深度克隆(递归复制所有可变类型)很有用,但它只能由其成分为这种体系结构设计的类型执行。在许多情况下,真正的深度克隆是过度的,并且它可能干扰所需的事实上是一个对象,其可见内容指的是与另一个对象相同的对象(即语义级复制)。如果一个对象的可见内容是从其他对象递归派生的,那么语义级克隆就意味着一个递归的深度克隆,但是在可见内容只是一些泛型类型的情况下,代码不应盲目深度克隆一切看起来它可能是深度可克隆的。