深度拷贝、浅拷贝、克隆

2022-08-31 13:17:40

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


答案 1

不幸的是,“浅拷贝”,“深拷贝”和“克隆”都是定义不清的术语。


在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 = ... 

通常的解释是,的“浅层”副本将是一个新对象,其等于1并且其字段引用与原始数组相同的数组;例如:eg1Examplefoobar

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

对 “深”副本的正常解释是一个新对象,其等于 1,其字段引用原始数组的副本;例如:eg1Examplefoobar

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

(来自C / C++背景的人可能会说,参考分配会产生浅层副本。但是,这不是我们通常所说的Java上下文中的浅层复制的意思......

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

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

  • 封装的数据类型呢?例如,字符串?字符串实际上不仅仅是一个对象。实际上,它是一个具有一些标量字段的“对象”,以及对字符数组的引用。但是,字符数组被 API 完全隐藏。那么,当我们谈论复制字符串时,将其称为“浅”副本或“深”副本是否有意义?或者我们应该称之为副本吗?


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

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

  • 事实上,规范甚至没有具体说明克隆会生成一个新对象。

以下是javadoc所说的:

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

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

简而言之,克隆对于每个Java类来说可能意味着不同的东西。


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


答案 2

术语“克隆”是模棱两可的(尽管Java类库包括一个可克隆的接口),可以指深层副本或浅副本。深/浅副本并不专门绑定到 Java,而是与制作对象副本相关的一般概念,指的是如何复制对象的成员。

例如,假设您有一个人类:

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

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

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

尽管上面的示例微不足道,但深度副本和浅副本之间的差异非常重要,并且对任何应用程序都有很大的影响,特别是如果您尝试提前设计通用克隆方法,而不知道以后有人可能会如何使用它。有时你需要深度或浅层语义,或者一些混合的语义,你深度复制一些成员,而不是其他成员。