オブジェクトの浅いコピー
プログラミングC# p78
C#では、インタフェースICloneableを実装して、Cloneメソッド中でMemberwiseCloneメソッドを呼べば浅いコピーが出来ると記述してあります。
class SomeType : ICloneable { public Object Clone() { return MemberwiseClone(); } }
Javaでどうすべきかは、Java言語規定に書いてありました。
Java言語規定 / 20.1.5 protected Object clone()
メソッド clone は,クラス Object で実装されている。クラス Object の メソッド clone は,インタフェースCloneableを実装するサブクラス用の汎用ユーティリティとして実装されている。メソッド cloneは,上書きされることがある。この場合,次の呼び出しを行えば,上書きした定義から上書きされたクラス Object の メソッド clone を参照できる。super.clone()
つまりこういうことですね。
class SomeType implements ICloneable { public Object clone() throws CloneNotSupportedException { return super.clone(); } }
じゃあ一体、Object#cloneって何なのよ?ってソース見てみたら、こんな宣言でした。
protected native Object clone() throws CloneNotSupportedException;
というわけで、nativeでよろしくやってくれているようです。
サンプル書きたかったが時間切れ。明日気が向いたら書こう。(そして、多分気が向かない。。)
浅いコピーの例
using System; class PC : ICloneable { public string maker; public MotherBoard mb; public Object Clone() { return MemberwiseClone(); } public override string ToString() { return "pc.maker = " + this.maker + ", mb.maker = " + mb.maker; } } class MotherBoard { public string maker; } class MainClass { static void Main() { PC myPc = new PC(); myPc.maker = "IBM"; myPc.mb = new MotherBoard(); myPc.mb.maker = "ASUS"; PC yourPc = (PC)myPc.Clone(); Console.WriteLine("myPc = " + myPc); Console.WriteLine("yourPc = " + yourPc); myPc.maker = "NEC"; myPc.mb.maker = "Gigabyte"; Console.WriteLine("myPc = " + myPc); Console.WriteLine("yourPc = " + yourPc); } }
結果はこう。
myPc = pc.maker = IBM, mb.maker = ASUS yourPc = pc.maker = IBM, mb.maker = ASUS myPc = pc.maker = NEC, mb.maker = Gigabyte yourPc = pc.maker = IBM, mb.maker = Gigabyte
myPcのmakerとマザーボードのmakerを書き換えただけのつもりだったのに、yourPCのマザーボードのmakerも書き換わってしまっています。
これは、myPcを浅いコピーした結果、myPc.mbの参照がyourPc.mbにコピーされたため、myPc.mbもyourPc.mbも同じオブジェクトを参照する結果となり、myPc.mb.makerを変更した結果はyourPc.mb.makerにも反映されたというわけです。
じゃあ、浅いコピーじゃなくて深いコピーをするにはどうするか?こんな風に真面目に(?)書くしかないでしょう。
using System; class PC : ICloneable { public string maker; public MotherBoard mb; public Object Clone() { PC clonePC = new PC(); clonePC.maker = this.maker; clonePC.mb = new MotherBoard(); clonePC.mb.maker = mb.maker; return clonePC; } public override string ToString() { return "pc.maker = " + this.maker + ", mb.maker = " + mb.maker; } } class MotherBoard { public string maker; } class MainClass { static void Main() { PC myPc = new PC(); myPc.maker = "IBM"; myPc.mb = new MotherBoard(); myPc.mb.maker = "ASUS"; PC yourPc = (PC)myPc.Clone(); Console.WriteLine("myPc = " + myPc); Console.WriteLine("yourPc = " + yourPc); myPc.maker = "NEC"; myPc.mb.maker = "Gigabyte"; Console.WriteLine("myPc = " + myPc); Console.WriteLine("yourPc = " + yourPc); } }
この結果はこう。
myPc = pc.maker = IBM, mb.maker = ASUS yourPc = pc.maker = IBM, mb.maker = ASUS myPc = pc.maker = NEC, mb.maker = Gigabyte yourPc = pc.maker = IBM, mb.maker = ASUS
期待したとおり、myPcの内容だけ変更されました。
オブジェクトをClone(複製)するときは、こういうことを意識しなければならないってことですね。