Smalltalk中Array的shallow copy工作原理及代码异常问询
copy方法的实际行为解惑 我原本以为向数组发送copy消息时会执行浅拷贝,理论上两个对象应指向同一数组的元素,修改其中一个数组的元素应该会影响另一个。但运行下面的代码后,结果却看起来像是深拷贝——两个数组相互独立,修改彼此没有影响。想请教一下copy方法实际的工作机制是怎样的?
测试代码
| a b | a := #('first' 'second' 'third'). b := a copy. Transcript show: a = b;cr. Transcript show: a == b;cr. Transcript show: (a at: 1) == (b at: 1);cr. b at: 1 put: 'newFirst'. Transcript show: a;cr. Transcript show: b;cr. Transcript show: (a at: 1) == (b at: 1);cr. a at: 2 put: '2nd'. Transcript show: a;cr. Transcript show: b;cr. a := nil. Transcript show: a;cr. Transcript show: b;cr.
运行结果
true false true #('first' 'second' 'third') #('newFirst' 'second' 'third') false #('first' '2nd' 'third') #('newFirst' 'second' 'third') nil #('newFirst' 'second' 'third')
别急,其实你看到的现象并不是copy做了深拷贝,而是你混淆了修改数组元素引用和修改元素本身的区别。让我一步步拆解:
copy确实是浅拷贝
从运行结果里的(a at: 1) == (b at: 1)一开始返回true就能看出来——拷贝后的数组b和原数组a的初始元素是指向同一个字符串对象的。这完全符合浅拷贝的定义:新数组是一个新的容器,但容器里装的还是原来的元素引用。为什么修改元素后看起来独立?
你执行的b at: 1 put: 'newFirst'并不是修改了原来的字符串对象,而是把b数组的第一个位置的引用换成了一个全新的字符串对象。原数组a的第一个位置仍然指向原来的'first',所以两者自然就不一样了。反过来,如果数组里的元素是可变对象(比如另一个数组或者自定义的可变类实例),你去修改这个元素的内部状态,那两个数组都会看到变化。比如试试这个:
| a b subArray | subArray := #('inner') asOrderedCollection. a := {subArray}. b := a copy. subArray add: 'changed'. Transcript show: a; cr. "会显示#(('inner' 'changed'))" Transcript show: b; cr. "同样显示#(('inner' 'changed'))"这时候就能看到浅拷贝的特性了——因为两个数组共享同一个子数组元素,修改子数组的内部状态会影响两者。
再看你的代码细节
a == b返回false:这说明a和b是两个不同的数组对象,这正是copy的作用——创建一个新的数组容器。- 当你执行
a at: 2 put: '2nd'时,同样是给a的第二个位置换了新的字符串引用,b的第二个位置还是原来的'second',所以两者各自独立。 - 最后把
a设为nil,b依然存在,这也说明它们是两个独立的对象,只是最初共享元素引用而已。
总结一下:copy对数组做的是浅拷贝——新数组是新对象,但元素和原数组共享引用。你看到的"独立"是因为你替换了数组里的元素引用,而不是修改元素本身的状态。如果元素是可变对象,修改元素内部状态时,两个数组才会同时反映变化。
内容的提问来源于stack exchange,提问作者On The Net Again

