【译】Ruby中的对象拷贝-dup vs clone
翻译一篇简单文章,并附上一些个人测试结果。总是记不住
dup
跟clone
的区别?这篇文章或许可以帮到你,翻译自https://www.rubyguides.com/2018/11/dup-vs-clone/ 。
你是否知道你可以拷贝Ruby中的对象?不仅如此,你还能用两种不同的方法来做这件事情。
两种方法分别是:
- dup
- clone
我们立马会探讨它们之间的差别,不过首先...
为什么你想要去拷贝一个对象呢?
Ruby中的许多对象都是可变的,你可以去修改它们。
如果你想改变一个对象,却还想以副本的方式保留它原有的样子,那你可以去拷贝它。
举个例子:
你可能会想得到一个数组,它包含某个数组中除了第一个元素之外的所有元素。
你可以用这种方式来实现:
a = [1,2,3,4,5]
a[1..-1] # => [2,3,4,5]
或者还有另一种方式:
b = a.clone
b.shift # => [1]
b # => [2,3,4,5]
上述两个例子都让你能够保留最原始的数组。
被冻结的对象
dup
与clone
两个方法并不是彼此的别名,这有别于Ruby中其他常用的方法map/collect,它们之间还是有些许不同的。
窥探两个事物之间的相同点和不同点,是加深对它们理解的好的方法。
两个方法都是用来拷贝对象,不同之处在于dup
并不拷贝对象的属性。
什么是对象的属性?
- 冻结的状态
- 受污染的状态(Object#tainted?,这个属性Object#dup会拷贝,应该是作者忘了提醒了)
- 单例类
下面有个例子:
a = Object.new.freeze
b = a.dup
b.frozen? => # false
b = a.clone
b.frozen? => # true
Ruby2.4包含一个可选项,让clone
在拷贝对象的时候能够忽略掉冻结状态,下面是例子:
a.clone(freeze: true)
a.clone(freeze: false)
译者附加研究结果
根据研究,新版本Ruby的Object#dup
方法会拷贝对象的tainted
状态(其实旧版本也会拷贝)
dup copies the tainted state of obj.
以下是我在Rubu1.8跟Ruby2.6环境的测试结果
# ruby 1.8.7
> a = Object.new.taint
=> #<Class:0x7fd987e11028>
> a.tainted?
=> true
> a.dup.tainted?
=> true
# ruby 2.6.5
> a = Object.new.taint
=> #<Object:0x00007fe3011afec8>
> a.tainted?
=> true
> a.dup.tainted?
=> true
所以Object#dup
不会拷贝的属性应该要把它给划掉
- 冻结的状态
- ~~受污染的状态~~
- 单例类
以下是代码示例:
a = Object.new.taint
a.tainted? => true
a.dup.tainted? => true
def a.hello
"hello"
end
a.hello => "hello"
a.dup.hello => NoMethodError (undefined method `hello' for ....)
a.clone.hello => "hello"
为何不测试2.7?因为2.7之后taint的检测会被移除掉,后面也打算移除掉这个属性,所以可以不必太关注这个了。2.7测出来的结果也是有点迷,tainted
状态直接不可用:
# ruby 2.7.1
> a = Object.new.taint
> a.tainted?
=> false
对比深拷贝跟浅拷贝
还有比你所能见到的更多的拷贝行为。
不管是采用dup
还是clone
,当你进行拷贝的时候,你正在做的都是浅拷贝。
这意味着该对象所包含的其他对象并不会被拷贝。
换句话说:
如果你有一个由字符串组成的数组,你对它进行拷贝,那么只有数组本身会被拷贝,它自身所包含的字符串却不会。
可以自己看看:
original = %w(apple orange banana)
copy = original.clone
original.map(&:object_id)
# [23506500, 23506488, 23506476]
copy.map(&:object_id)
# [23506500, 23506488, 23506476]
即便是在数组拷贝了之后,它们所包含的数组对象的id都是一致的,因此它们是相同的字符串。
针对这个场景,你可以这样解决:
strings.clone.map(&:clone)
结果就是无论是数组本身还是它所包含的字符串都会被拷贝,不过请记住,这种方式只适用于一个层级深度的拷贝。作为替代品,你可以尝试使用ActiveSupport中的deep_dup。
总结
你已经学会在Ruby中拷贝对象的方法了!这篇文章所讲述的东西包含了dup
跟clone
这两个方法的异同,以及深拷贝和浅拷贝之间的区别。感谢您的阅读。