QA@IT

Rubyの文字列のコピーで挙動が分かりません

3124 PV

Rubyの文字列のコピーで挙動が分かりません

str = ["りんご", "バナナ", "スイカ"]
str2 = str
str2[1] = "メロン"
p str2
p str

とすると、

["りんご", "メロン", "スイカ"]
["りんご", "メロン", "スイカ"]

というようにstrもstr2も、どちらもバナナがメロンに置き換わってしまいます。str2にstrの内容をコピーしているのにどうしてこなるのでしょうか?

回答

str2 = str はオブジェクトに対する参照のコピーを行っているからです。オブジェクトそのものをコピーするためにはObject#dupやObject#cloneを使う必要があります。

REPL(irb,pryなど)で実行してみましょう。

str  = ["りんご", "バナナ", "スイカ"]
str2 = str
p str.object_id
# => 70289581420820
p str2.object_id
# => 70289581420820

Object#object_idは、オブジェクトに対して割り当てられたユニークな整数を返します。つまり、これが同一であれば「同一のオブジェクト」なのです。

上記の例ではstrとstr2のobject_idは同じです。つまり、strもstr2も同一のオブジェクト(この場合はArray)の参照を保持している変数なのです。

オブジェクトへの参照ではなく、オブジェクトそのものをコピーするには、Object#dupやObject#cloneを使います。dupとcloneの違いはリファレンスマニュアルを参照してください。

str3 = str.dup
p str3.object_id
# => 70289581646200

str3はstr / str2と違うオブジェクトです。では、質問にあった代入をしてみましょう。

str2[1] = "メロン"
p str
# => ["りんご", "メロン", "スイカ"]
p str2
# => ["りんご", "メロン", "スイカ"]
p str3
# => ["りんご", "バナナ", "スイカ"]

ただし、マニュアルにもあるようにObject#dupやObject#cloneは「浅いコピー(shallow copy)」を行います。

clone や dup はオブジェクト自身を複製するだけで、オブジェクトの指している先(たとえば配列の要素など)までは複製しません。これを浅いコピー(shallow copy)といいます。

つまり、配列の各要素はコピーされていない、ということです。確認してみます。

p str[0].object_id
# => 70289581421240
p str2[0].object_id
# => 70289581421240
p str3[0].object_id
# => 70289581421240

object_idが同一ですので、破壊的な変更を加えると…

str[0].sub! /り/, 'だ' # 
p str
# => ["だんご", "メロン", "スイカ"]
p str2
# => ["だんご", "メロン", "スイカ"]
p str3
# => ["だんご", "バナナ", "スイカ"]

…となります(Sting#sub!は破壊的な置換を行う)。

深いコピー(deep copy)が必要な場合、リファレンスマニュアルには一例としてMarshalモジュールを利用する方法が紹介されています。

編集 履歴 (1)
ウォッチ

この質問への回答やコメントをメールでお知らせします。