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