深拷贝,浅拷贝,克隆

trs*_*trs 73 java clone

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

Ste*_*n C 110

不幸的是,"浅拷贝","深拷贝"和"克隆"都是相当不明确的术语.


在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);
Run Code Online (Sandbox Code Playgroud)

简而言之,对类型为引用类型的变量的引用的赋值是"复制值",其中值是对象引用.要复制一个对象,需要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 = ... 
Run Code Online (Sandbox Code Playgroud)

正常的解释是,"浅"副本eg1将是一个新的Example对象,其foo等于1,其bar字段指的是与原始对象相同的数组; 例如

Example eg2 = new Example(eg1.foo, eg1.bar);
Run Code Online (Sandbox Code Playgroud)

"深"副本的正常解释eg1是一个新的Example对象,其foo等于1,其bar字段指是原始数组的副本 ; 例如

Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


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

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

  • 实际上,规范甚至没有具体说明克隆产生了一个新对象.

这是javadoc所说的:

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

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

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


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

  • "x.clone().equals(x)`应该被认为是真的这个概念对我来说似乎很奇怪.对于所有对象类型,我可以想到的`equals`的唯一含义是等价的,并且不应该认为可变类型的实例与任何其他实例等效.如果一个对象是不可变的,那么没有理由克隆它,如果一个对象是可变的,它不应该等同于它的克隆. (2认同)

Ada*_*kin 22

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

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

class Person {
    String name;
    List<String> emailAddresses
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


小智 18

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

  • 实际上`o.clone()== o`可以是真的; 看到我的回答. (4认同)