Java - 实现数组的深拷贝和浅拷贝

8
我正在尝试理解Java中浅拷贝和深拷贝的概念。有很多关于这个主题的文章和问答,但每当我尝试在真实的Java代码中实现这些概念时,一切都变得不清楚了。
其中一个回答是我理解的基础,可以在此链接中通过示意图来解释深拷贝和浅拷贝。
下面我将展示每种情况的实现:
- 浅拷贝:
我以System.arraycopy()方法为例,因为我在许多文章中读到它执行浅拷贝(还有clone方法)。
public class Test {

    public static void main(String[] args) {
        NameValue[] instance1 = {
                new NameValue("name1", 1),
                new NameValue("name2", 2),
                new NameValue("name3", 3),
        };
        NameValue[] instance2 = new NameValue[instance1.length];

        // Print initial state
        System.out.println("Arrays before shallow copy:");
        System.out.println("Instance 1: " + Arrays.toString(instance1));
        System.out.println("Instance 2: " + Arrays.toString(instance2));

        // Perform shallow copy
        System.arraycopy(instance1, 0, instance2, 0, 3);

        // Change instance 1
        for (int i = 0; i < 3; i++) {
            instance1[i].change();
        }

        // Print final state
        System.out.println("Arrays after shallow copy:");
        System.out.println("Instance 1: " + Arrays.toString(instance1));
        System.out.println("Instance 2: " + Arrays.toString(instance2));
    }

    private static class NameValue {
        private String name;
        private int value;

        public NameValue(String name, int value) {
            super();
            this.name = name;
            this.value = value;
        }

        public void change() {
            this.name = this.name + "-bis";
            this.value = this.value + 1;
        }

        @Override
        public String toString() {
            return this.name + ": " + this.value;
        }
    }
}

执行主方法的结果如下:
Arrays before shallow copy:
Instance 1: [name1: 1, name2: 2, name3: 3]
Instance 2: [null, null, null]
Arrays after shallow copy:
Instance 1: [name1-bis: 2, name2-bis: 3, name3-bis: 4]
Instance 2: [name1-bis: 2, name2-bis: 3, name3-bis: 4]

这个结果与前面链接的架构相符:浅拷贝
  • 深拷贝:

我以Arrays.copyOf()方法为例,因为我在许多文章中读到它执行深拷贝(以及Arrays.copyOfRange方法)。

public static void main(String[] args) {
    NameValue[] instance1 = {
            new NameValue("name1", 1),
            new NameValue("name2", 2),
            new NameValue("name3", 3),
    };
    NameValue[] instance2 = new NameValue[instance1.length];

    // Print initial state
    System.out.println("Arrays before deep copy:");
    System.out.println("Instance 1: " + Arrays.toString(instance1));
    System.out.println("Instance 2: " + Arrays.toString(instance2));

    // Perform deep copy
    instance2 = Arrays.copyOf(instance1, 3);

    // Change instance 1
    for (int i = 0; i < 3; i++) {
        instance2[i].change();
    }

    // Print final state
    System.out.println("Arrays after deep copy:");
    System.out.println("Instance 1: " + Arrays.toString(instance1));
    System.out.println("Instance 2: " + Arrays.toString(instance2));
}

显示以下内容:

Arrays before deep copy:
Instance 1: [name1: 1, name2: 2, name3: 3]
Instance 2: [null, null, null]
Arrays after deep copy:
Instance 1: [name1-bis: 2, name2-bis: 3, name3-bis: 4]
Instance 2: [name1-bis: 2, name2-bis: 3, name3-bis: 4]

如果我们基于之前的模式来实现深拷贝逻辑,应该得到以下结果: Deep copy 正如您所看到的,主方法执行的结果与上面模式的逻辑不同。
欢迎任何解释。

哇,你在这里真的花了很多时间。希望你没有提供太多信息(例如我,我更倾向于跳到下一个问题,因为我需要大约10分钟来消化你的输入)。但只是为了好玩:尝试使用克隆方法而不是copyOf()(我认为它不会浪费时间克隆东西,而只是进行浅复制)。 - GhostCat
3
Arrays.copyOf进行浅复制。 - Christopher Schneider
只有比较两个实例,你才能确定它们是深拷贝还是浅拷贝。基本上,它们应该具有“相同的内容”,但如果是深拷贝,则不应引用同一对象。 如果你使用Java(或其他语言)进行开发,你应该了解一些关于引用和其他相关知识的内容。 - sascha10000
可能是Does Arrays.copyOf produce a shallow or a deep copy?的重复问题。 - Andreas
1
@sascha10000,我曾经有误解,因为我基于Arrays.copyOf执行深拷贝的事实来构建我的逻辑。现在,既然你们中的许多人确认它生成浅拷贝,事情就更清楚了。 - Strider
3个回答

5

我不知道你在哪里读到copyOf()可以执行深拷贝,因为那是完全错误的。

引用Arrays.copyOf(T[] original, int newLength)的Javadoc:

对于原始数组和副本中都有效的所有索引,两个数组将包含相同的值

这意味着这是浅拷贝。要进行深拷贝,值必须指向不同的对象,因为所引用的对象也必须是一份拷贝。

要执行深拷贝,必须迭代数组并复制值。Java无法为您完成此操作,因为它不知道如何复制对象。

例如,Java如何知道如何复制NameValue对象?clone()?复制构造函数?序列化+反序列化?工厂方法?其他方式?


1
补充一下,如果你搜索copyOf()方法的源代码,你最终会找到这个:System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); - 最终结果是一样的 :P - Rafael Lins
Java在深度复制方面非常糟糕。正如Andreas所说,你必须自己迭代和复制所有内容。你可以尝试Cloneable的东西,但它实际上并不起作用。 - Rafael Lins
如果这些值不是引用,那该怎么办?引用中明确表示两个数组包含相同的值,而非相同的引用。3和3是相同的值,并不意味着它们是相同的引用。 - Dispersia
4
@Andreas @g0dkar 我从Richard M. Reese的书《Oracle认证Java SE 7程序员学习指南》中得到了这些信息。在第4章“复制数组”的总结中,它评论说使用Arrays.copyOf方法执行整个数组的深层复制...基于这个信息,我没有去寻找Arrays.copyOf方法的实现。 - Strider
1
@Dispersia 原始值是普通的值,浅复制和深复制并不适用。该值只是简单地被复制。这种区别适用于对象引用,区分复制引用(浅复制)和复制对象(深复制)。 - Andreas
显示剩余5条评论

4
我正在尝试理解Java中浅拷贝和深拷贝的概念。 在Java中,您传递并存储对象的引用而不是对象本身。 因此,当您有一个NameValue[] array数组时,该数组不包含NameValue对象,而是对对象的引用。 因此,当您对NameValue[] array2进行浅拷贝时,这意味着您只是将一个数组中的引用复制到另一个数组中。这有效地意味着现在arrayarray2都指向完全相同的对象,并且您从array[2]所做的任何更改都将从array2 [2](同一对象)可见。 当您执行深拷贝时,您会完全复制每个对象到另一个内存区域,并在新数组中保留对该新对象的引用。 这样,这两个数组现在引用不同的对象,并且对array[2]的任何更改都不会从array2[2]可见。 更新: 这不适用于原始类型,它们确实存储实际值而不是引用。 因此,当您复制int[] a时,您会获得值的副本(在某种意义上是深拷贝),因为a[2]包含值本身而不是对该值的引用。

1
好的解释。不过应该有人提到,JDK中的每个复制方法实际上都会复制其内容。但是浅拷贝和深拷贝之间的区别在你复制对象数组而不是基本类型时变得明显起来。 - Janez Kuhar
这正是我一直在寻找的解释,特别是Java处理使用基元与使用引用复制数组的方式...除了@Andreas的答案之外,我认为一方面,我在问题中提到的方法在使用带有引用的数组时会产生浅层复制,另一方面,在使用基元时它们会产生“更像深层复制”的效果。 - Strider
原始变量是内存中包含您分配的值的位置。对象变量是内存中包含对实际对象位置的引用的位置。因此,当您分配一个变量时,您将一个位置的内容移动到另一个位置。在原始变量的情况下,内容本身就是值。在引用的情况下,内容是对象的引用。 - Jim

1
我认为有一些误解,即Arrays.copyOf()会生成深拷贝。 Arrays.copyOf()创建一个包含旧对象引用的新数组,这些引用并未被复制。正如我添加的链接所解释的那样,在嵌套数组的情况下,它们将不会被复制,因此不能被视为深拷贝而是浅拷贝。
请参见this获取更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接