java-beginner.com ブログ

プログラミングを学習するブログ(Javaをメインに)

Objectクラスのcloneメソッド

投稿日:

最終更新日:2016年10月05日

アイキャッチ

こんにちは。「Javaを復習する初心者」です。

今回はObjectクラスのcloneメソッドのオーバーライドの仕方を確認しました。

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

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

ItemクラスとPersonクラス

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

Item

public class Item {

    public String name;

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

}

Person

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の場合は不変オブジェクトなのでそのまま参照を格納して大丈夫のようです。