Rubyでプログラミングをしていると、オブジェクトコピーの方法が複数あり、どの方法を使うべきか迷う場合もあります。本記事では、Rubyのオブジェクトコピーの中からdupとcloneを取り上げます。まずは、dupとcloneの違いを、使いながら覚えましょう。
Rubyのdupメソッドとは
Rubyのプログラムの中で、オブジェクトをコピーする方法は複数ありますが、「dup」はオブジェクトのコピーができるメソッドの1つです。
Rubyのメソッドでオブジェクトのコピーが可能なものに「clone」もあります。dupメソッドとcloneメソッドは、どちらもオブジェクトのコピーができるメソッドですが、それぞれの使い方・目的には違いがあるので、双方の機能を比較しながら、使い方を覚えていきましょう。
cloneメソッドとの違い
dupメソッドもcloneメソッドもRubyの中でオブジェクトのコピーができるメソッドとして知られていますが、2つのメソッドの明らかな違いとしては、以下が挙げられています。
【オブジェクトの状態】
・cloneメソッドは、凍結・汚染・信頼をコピーする
・dupメソッドは、汚染・信頼をコピーする
【オブジェクトの特異メソッド】
・cloneメソッドは、コピーする
・dupメソッドは、コピーしない
凍結状態 | 汚染状態 | 信頼状態 | 特異メソッド | |
---|---|---|---|---|
clone | 〇 | 〇 | 〇 | 〇 |
dup | × | 〇 | 〇 | × |
特異メソッドとは
Rubyで「特異メソッド」と呼ばれるものは、特定のオブジェクトに紐づくメソッドです。実際のコード記述例を挙げて、解説します。
【コード記述例】
a = [1,2,3]
b = [1,2,3]
def a.jijyou
self.map { |b| b * b }
end
a.jijyou
=> [1, 4, 9]
b.jijyou
=> NoMethodError: undefined method `jijyou' for [1, 2, 3]:Array
2つの実行結果の違い
コード記述例を実行すると、a.jijyouについては、メソッドの処理結果が表示され、b.jijyouについてはエラーが返されています。メソッドの定義を確認してみると、「a.jijyou」と記述されており、オブジェクト(a)まで含めて定義されていることがわかります。
特異メソッドとは、こうしたオブジェクトまで含めて動作が紐づけられるメソッドのことをいいます。
dupメソッドとcloneメソッドを使ってオブジェクトを複製する方法
Rubyでオブジェクトのコピーを行いたい場合、dupメソッドを使っても、cloneメソッドを使っても、構文は変わりません。
copyObj1 = object.dup
copyObj2 = object.clone
dupメソッドもcloneメソッドもRubyのオブジェクト複製メソッドとしては、「浅いコピー」と呼ばれています。「浅いコピー」に対する「深いコピー」ではオブジェクトの参照先までコピーします。
cloneを使ってオブジェクトを複製する方法
ここでは、cloneを使ってオブジェクトを複製する際のコード記述例と実行結果をご紹介します。
【コード記述例】
list1 = [{key:'aa'},{key:'bb'},{key:'cc'}]
list2 =list1.clone
list1.object_id
=> 70343473688700
list2.object_id
=> 70343473655820
複製結果の確認①
コード記述例では、cloneで複製したオブジェクトのobject_idが複製元のobject_idと異なることを確認しておきます。その後、複製元(list1)のデータを並び替えて、オブジェクトの内容を確認します。
実行結果を確認すると、複製したオブジェクトの内容は、複製直後と変わらず、変更した複製元とは異なる内容であることが確認できます。
実行結果 |
---|
list1.reverse! => [{:key=>”cc”}, {:key=>”bb”}, {:key=>”aa”}] list2 => [{:key=>”aa”}, {:key=>”bb”}, {:key=>”cc”}] |
複製結果の確認②
次に別の観点で、複製したオブジェクトを確認してみましょう。複製元のデータの一部を変えてみます。reverse!で並び順を変えた複製元のオブジェクトのデータを一部別の値に変えてみます。
実行結果では、複製したオブジェクトは、並び順の変更では影響を受けませんが、データの値が変わると、複製先のデータまで変わっています。
複製元と複製先で、オブジェクトは異なりますが、データの参照先は同じであるためです。
実行結果 |
---|
list1[0][:key]='99' |
dupを使ってオブジェクトを複製する方法
ここでは、dupを使ってオブジェクトを複製する際のコード記述例をご紹介します。
【コード記述例】
list1 = [{key:'xx'},{key:'yy'},{key:'zz'}]
list2 = list1.dup
list1.object_id
=> 70343473688700
list2.object_id
=> 70343473670940
複製結果の確認
dupメソッドを使った場合も、複製元と複製先では、異なるオブジェクトを参照しています。cloneメソッドで行った検証のように、複製元のオブジェクトの並び替えとデータ変更をしてみると、得られる結果は、cloneメソッドと同じ内容になります。
「浅いコピー」という共通点があるので、一見cloneとdupの動作は同じになります。
深いコピーとは?
clone や dup はオブジェクトそのものを複製するだけで、オブジェクトが指し ている配列の要素などまでは複製しません。オブジェクトが指している先まで参照するコピーを「深いコピー」と呼んでいます。
cloneやdupなどの浅いコピーは「shallow copy」と呼ばれます。それに対し、深いコピーを「deep copy」と呼びます。深いコピーが必要なとき、RubyではMarshalモジュールを使います。
深いコピーのMarshalモジュール
深いコピーでは、 Marshalモジュールを利用して「Marshal.load(Marshal.dump(obj))」という形式で複製を作成します。詳細な実装方法については、Marshalに関する詳細記事を参照してください。Marshalは、オブジェクトによっては使えない場合もあります。
Rubyのコピー処理にはdupとcloneを使う
オブジェクトの複製には、浅いコピーと深いコピーがあり、その用途に応じて、メソッドで対応できたり、モジュールを使う必要があったりします。浅いコピーと深いコピーの違いについても、初心者にはわかりづらく、難しく感じることでしょう。
Rubyでプログラミングを始めたばかりという人は、まずは浅いコピーのdupとcloneを使ってみて、その動作を検証しながら、これらのメソッドを確実に使いこなしましょう。