为什么 map 函数在对每个元素的属性进行操作时会改变对象数组?

Why does map function mutate array of objects when operating on each element's attribute?

提问人:Jin 提问时间:3/25/2019 最后编辑:sawaJin 更新时间:3/25/2019 访问量:271

问:

我有一个对象数组:

class Person
  attr_accessor :email

  def initialize(email)
    @email = email
  end
end

array = [
  Person.new('[email protected]'),
  Person.new('[email protected]')
]

我从原始数组创建了一个克隆来执行映射功能,然后我映射了每个元素以使其 email 属性变为大写:

clone = array.clone
clone.map { |obj|
  obj.email.upcase!
  obj
}

puts array.inspect # why is the original being mutated
puts clone.inspect

它改变原始数组。我已经尝试了两者。我得到同样的结果。为什么在对每个元素的属性进行操作时会改变对象?dupclonemap

阵 列 红宝石 可变

评论

0赞 sawa 3/25/2019
永远不要做,因为它等同于 ,你应该使用后者。puts foo.inspectp foo
2赞 Aleksei Matiushkin 3/25/2019
“你应该使用后者”——谁说的?
0赞 engineersmnky 3/25/2019
@sawa,这怎么是“等价物”呢? 将打印对象并返回 。 将打印对象(通过 Inspect)并返回对象。puts foo.inspectnilp foo
0赞 sawa 3/26/2019
@engineersmnky我说得不准确。它们的输出是相同的,就此而言,它们没有区别。

答:

3赞 Amadan 3/25/2019 #1

您克隆了包含引用的数组,但未更改该数组;您更改了实例本身。 就是所谓的“浅克隆”,它只复制接收方对象,而不复制它可能包含的引用对象。PersonPersonclone

在现实世界的逻辑中:你拿了一张纸,上面写着“珍妮,蒂米”。然后你把它复制到另一张纸上。然后你拿起第一张纸,找到它所指的人,并给了他们一个苹果。然后你拿起第二张纸,找到上面的人,想知道他们的苹果是从哪里来的。但是只有一个蒂米,只有一个珍妮:你给第一个列表的珍妮一个苹果,第二个列表的珍妮也有一个苹果。

如果你想克隆一些东西,就克隆珍妮

array.map { |person|
  person.clone.yield_self { |clone|
    clone.email = clone.email.upcase
  }
}

(请注意,我没有使用 .原因又是一样的:如果你克隆一个对象,它们都将使用相同的字符串。 更改该字符串,这将使克隆的电子邮件和原始的电子邮件都大写。因此,我们为克隆制作了一个新的电子邮件字符串。clone.email.upcase!emailupcase!

通过使用此工具逐步完成可视化,可以最好地理解此类内容。但是,该工具运行的是 Ruby 2.2,它不知道 ;此代码是等效的:yield_self

array.map { |person|
  clone = person.clone
  clone.email = clone.email.upcase
  clone
}

你也可以写这个,尽管它不会那么清晰地可视化:

array.map(&:clone).map { |clone|
  clone.email = clone.email.upcase
}