これも嵌りがちな内容。
コレクションのコピーについて。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (5件) を見る
項目16 コレクションを書き換える前に引数として渡すコレクションのコピーを作っておこう
ラジオのTunerを例に。
class Tuner def initialize(presets) @presets = presets clean end private def clean # 末尾が奇数のみを抽出 @presets.delete_if { |f| f[-1].to_i.even? } end end >> presets = %w(90.1 106.2 88.5) >> tuner = Tuner.new(presets) # 書き換えられちゃった! >> presets => ["90.1", "88.5"]
改善案(reject)
Array#delete_if
でなく Array#reject
を使えば良い。
でも、どっかで書き換えられるかもしれない。
改善案(clone/dup)
コピーすれば良い。
cloneはオブジェクトの状態(freezeと特メソッド)を残す。
dupは残さない。
大抵の場合はdupで良い。
class Tuner def initialize(presets) @presets = presets.dup clean end end
コピー時の注意点
dup/cloneはシャロー(shallow)コピーを返す。
コレクションクラスの場合、コンテナのコピーは作られるが、要素のコピーは作られない。
>> a = ["Polar"] >> b = a.dup << "Bear" => ["Polar", "Bear"] >> b.each { |x| x.sub!("lar", "oh") } => ["Pooh", "Bear"] # 書き換えられてる >> a => ["Pooh"]
deepコピーが欲しい場合
Marashalを使えば手軽にできるが、メモリも食うし、Marshalみ対応していないオブジェクト(IO、Fileなど)もあるので要注意。
>> a = ["Polar"] >> b = Marshal.load(Marshal.dump(a)) << "Bear" => ["Polar", "Bear"] >> b.each { |x| x.sub!("lar", "oh") } => ["Pooh", "Bear"] # 書き換えられていない! >> a => ["Polar"]