プログラマとプロマネのあいだ

プログラマもやるし、プロマネもやるし、たまに似非アーキとか営業っぽいこともやる

オブジェクトの浅いコピー

プログラミング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(複製)するときは、こういうことを意識しなければならないってことですね。