深拷贝与浅拷贝 Java

3
我是一个Java考试的学习者,我实践中发现的和理论教导的有所不同。
以下是代码:
    StringBuilder num3[]= new StringBuilder[2];
    num3[0]= new StringBuilder("Pine");
    num3[1]= new StringBuilder("Oak");
    StringBuilder num4[] = new StringBuilder[2];
    System.arraycopy(num3, 0, num4, 0, 2);
    System.out.println(num3[0]==num4[0]);
    System.out.println(num4[0]);
    num3[0] = new StringBuilder("Choc"); 
    System.out.println(num3[0]);
    System.out.println(num4[0]);

输出结果为:
true
Pine
Choc
Pine
其中的true表示这是一个浅拷贝,因为num4[0]与num3[0]引用同一个对象。但当我改变num3[0]时,我期望num4[0]也会改变。
如果这是一个浅拷贝,为什么会发生这种情况呢?
难道是因为num3[0]正在创建新对象,而旧的"Pine" StringBuilder对象被num4数组所引用?
如果是这样,请问是否有人能给出一个System.arraycopy的例子,其中表现出这种浅拷贝?
提前致谢, 克里斯扬奥费尔
5个回答

8

System.arraycopy后,这两个数组确实是彼此的浅拷贝。也就是说,你复制了对StringBuilder的引用。所引用的对象是相同的。

num3:  [ refA, refB ]                        num4: [ refA, refB ]
           |    |                                      |     |
           |    `-------> StringBuilder("Oak") <-------+-----' 
           `------------> StringBuilder("Pine") <------'

但是你改变了num3[0]中的引用,让它指向一个新的StringBuilder

num3:  [ refC, refB ]                        num4: [ refA, refB ]
           |    |                                      |     |
           |    `-------> StringBuilder("Oak") <-------+-----' 
           |              StringBuilder("Pine") <------'
           `------------> StringBuilder("Choc")

底层对象没有改变,你所做的只是改变了引用它们的内容。


System.arraycopy 就像使用 for 循环复制数组中的值一样,但对于大型数组来说更加高效(因为现代 CPU 有批量内存复制的特殊指令)。从语义上讲,你永远不需要它;它只是一种性能优化。


参考文献非常像指针,除了您无法对它们执行算术运算,并且它们在内存管理系统中保持所引用的内容存在。 - Donal Fellows

1

num3[0]num4[0]是指向同一对象的引用,因此当您更改num3[0]时,它只会更改num3[0]指向的位置,并不会更改num4[0]指向的位置。

System.out.println(num3[0]==num4[0]);

这是因为两个指向同一对象但各自独立,所以返回 true。
如果你做了类似的操作 num3[0].append("something") 并且打印了 num3[0]num4[0],那么两者都会受到影响。

1
你没有改变 num3[0],而是将其指向了另一个对象,也就是一个新的 StringBuilder 对象。num3[0] 和 num4[0] 不再指向同一个对象,这就是为什么 num4[0] 没有改变的原因。数组的引用值被复制,但如果你修改这些值,它不会影响到其他数组。真正会影响到它的是如果你修改它们所指向的对象,而不是引用
想象一下,num3[0] 和 num4[0] 像两个人指着一个背包。如果你往背包里加东西,他们都会指向一个被改变的东西。然而,如果你让 num3[0] 指向一个电视,num4[0] 仍然指向背包。

1

是因为针对num3 [0]创建了新对象,而num4数组引用了旧的“Pine”StringBuilder对象吗?

没错。

如果是这样,是否可以给我一个System.arraycopy的示例,其中显示了这个浅拷贝?

我不确定我理解了这个问题。无论是浅拷贝还是深拷贝,都不会影响您在复制之后所做的任何操作。

但请尝试以下操作:

num3[1].append("tree");
println(num4[1]);        // should be "Oaktree"

谢谢你的回答。我尝试了append并发现它们显然指向内存中的同一对象。但这使事情变得更加复杂,因为Arrays.copyOf应该是一个深拷贝,但它似乎现在正在将树附加到num4中。有任何想法为什么会发生这种情况吗? - HereToLearn
1
不,这不是深拷贝。谁说它是深拷贝了? - Ingo
Richard M Reese所著的Oracle认证Java SE 7程序员学习指南。 - HereToLearn
1
@HereToLearn 首先,你应该信任 API 文档,它说:“对于原始数组和副本中都有效的所有索引,两个数组将包含相同的值”。在深度复制中,重点是对象标识丢失了(通过克隆对象及其子对象)。仅从这一点就可以得出结论:这不是深度复制,因为 JVM 怎么知道如何深度复制某个任意对象呢?请注意,元素类型不受限于 Cloneable 或 Serializable。 - Ingo

0
在这种情况下,浅拷贝仅仅是一个指向对象的新引用。你有一个对象Pine,其中一个指针存储在num3中。现在你创建了一个浅拷贝(即创建一个新的引用),并将其存储在num4中。现在你有一个Pine对象和两个引用。当你将Choc赋值给num3时,引用数量减少1,你实际上用不同的引用替换了num3所持有的引用。Pine从未以任何方式被改变。

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