こんにちは。ハンドルネーム「Javaを復習する初心者」です。このサイトはプログラミング言語Javaの復習・学習をするブログです。プログラムの開発・実行はEclipseで行ってます。
スポンサーリンク
お知らせ
  • 参考文献のページ作りました。
  • Amazon.co.jpアソシエイトに参加していますが、参考文献の紹介はもしもアフィリエイトに統一しました。
  • 2016年10月9日からは投稿ペースを落とします。週1回くらいにする予定です。
スポンサーリンク

Objectクラスのcloneメソッド

こんにちは。「Javaを復習する初心者」です。今回はObjectクラスのcloneメソッドのオーバーライドの仕方を確認しました。

Objectクラスにcloneメソッドがあります。API仕様書には「このオブジェクトのコピーを作成して、返します。」という記述があるのですが、アクセス修飾子がprotectedです。このメソッドはpublicでオーバーライドして使うメソッドのようです。

cloneメソッドをオーバーライドする場合、Cloneableインターフェースの実装を宣言するのが一般的のようです。このインターフェースはメソッドを1つも持っていません。マーカーとしての役割を持つインターフェースです。

ItemクラスとPersonクラス

以下のようにItemクラスとPersonクラスを作ってみました。PersonメソッドはItemクラスを持っています。また、cloneメソッドをオーバーライドしています。ただし、問題があります。

public class Item {

    public String name;

    @Override
    public String toString() {
        return "Item [name=" + name + "]";
    }

}
public class Person implements Cloneable {

    public String name;
    public int age;
    public Item item;

    @Override
    public Object clone() throws CloneNotSupportedException {
        Person copy = new Person();
        copy.name = this.name;
        copy.age  = this.age;
        copy.item = this.item;
        return copy;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", item=" + item + "]";
    }

}

以下、ソースと実行結果です。

        Item item = new Item();
        item.name = "アイテム1";

        Person p1 = new Person();
        p1.name = "name1";
        p1.age  = 21;
        p1.item = item;

        Person p2 = null;
        try {
            p2 = (Person)p1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        System.out.println("コピー後");
        System.out.println("p1" + p1);
        System.out.println("p2" + p2);

        item.name = "アイテム2";

        System.out.println("アイテム変更後");
        System.out.println("p1" + p1);
        System.out.println("p2" + p2);
コピー後
p1Person [name=name1, age=21, item=Item [name=アイテム1]]
p2Person [name=name1, age=21, item=Item [name=アイテム1]]
アイテム変更後
p1Person [name=name1, age=21, item=Item [name=アイテム2]]
p2Person [name=name1, age=21, item=Item [name=アイテム2]]

コピー後の出力を見ると問題なさそうですが、Itemクラスのフィールドnameを変更するとコピー先のPersonクラスのインスタンスにも影響してしまします。なぜこのような結果になるかというと、cloneメソッドの「copy.item = this.item;」でItemクラスの参照を格納しているだけだからです。このようなコピーを浅いコピーと言います。

cloneメソッド

cloneメソッドを次のように変更するとこの現象は発生しません。

    @Override
    public Object clone() throws CloneNotSupportedException {
        Person copy = new Person();
        copy.name = this.name;
        copy.age  = this.age;

        Item itemCopy = new Item();
        itemCopy.name = this.item.name;
        copy.item = itemCopy;
        return copy;
    }

Itemクラスのインスタンスを新しく生成し、コピー先のPersonクラスのインスタンスに格納しています。Personクラスのコピー元とコピー先が持つItemクラスが異なる参照を持つことになりました。このようなコピーを深いコピーと言います。

Stringクラスのフィールドについては何も触れませんでしたが、Stringの場合は不変オブジェクトなのでそのまま参照を格納して大丈夫のようです。