【Java】独自クラスをCloneableインターフェース実装し、cloneメソッドでディープコピーする
【Java】独自クラスをCloneableインターフェース実装し、cloneメソッドでディープコピー
目的
親となる独自クラスを作成し、さらにその中で子となる独自クラスのコレクションを作成します。
そして親クラスをクローンすることで完全に複製が作成されることを目指します。
完全に複製が作成されることをディープコピーともいいます。シャローコピーというのもありますが、この記事ではディープコピーに焦点をあてています。
今回使用した環境
インターネット接続可能のオンラインの環境
64 ビット オペレーティング システム
Windows 10 22H2
Java 17
ソースコード
「目的」に書いてあることを読んでもイメージがつかみにくいため、実際のコードを例に見ていきましょう。
親となる独自クラスをCarとします。そして子となる独自クラスをPersonとします。
Carクラスはメンバとして名前、価格、乗っている人たち ( Personクラスのリスト ) を持っています。
import java.util.ArrayList;
public class Car implements Cloneable {
private String name; // 名前
private int price; // 価格(万円)
private ArrayList<Person> personList; // 乗っている人たち
public Car() {
personList = new ArrayList<Person>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public ArrayList<Person> getPersonList() {
return personList;
}
public void setPersonList(ArrayList<Person> personList) {
this.personList = personList;
}
@Override
protected Car clone() throws CloneNotSupportedException {
Car clone = (Car) super.clone();
// clone()しただけだとpersonListは同じアドレスを
// 参照してしまうので新しくインスタンスを作成
clone.setPersonList(new ArrayList<Person>());
// 新しく作成したリストにPersonインスタンスのコピーを追加
for (Person person : this.personList) {
clone.getPersonList().add(person.clone());
}
return clone;
}
}
public class Person implements Cloneable {
private String name; // 名前
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
ここでキモとなるのはCar, PersonクラスがCloneableのインターフェースを実装していることと、cloneメソッドをオーバーライドしていることです。
特に重要なのはCarクラスのcloneメソッド。CarクラスではメンバとしてpersonListのコレクションを持っていますが、cloneしただけだとpersonListのコレクションはコピーされません。(※)
(※)厳密にいうとコピーされているのですが、シャローコピー (参照先は同じ) となっています。
なので独自でコピー処理をしてやる必要があります。
こちらは動作確認のためのCarMainクラスです。
public class CarMain {
public static void main(String[] args) {
try {
// 乗員を作成
Person kogorou = new Person("小五郎");
Person ran = new Person("蘭");
Person konan = new Person("コナン");
Person guest = new Person("初見依頼客");
// 1番目の車に小五郎と初見依頼客を乗せる
Car firstCar = new Car();
firstCar.setName("スポーツカー");
firstCar.setPrice(300);
firstCar.getPersonList().add(kogorou);
firstCar.getPersonList().add(guest);
// firstCarのインスタンスをクローンしてsecondCarを作成
Car secondCar = firstCar.clone();
System.out.println("■clone()直後の状態確認");
printCar(firstCar, secondCar);
// 2番目の車の乗客を初見依頼客⇒常連依頼客に変更
// さらに蘭とコナンを乗せる
secondCar.setName("ファミリーカー");
secondCar.setPrice(500);
int lastIndex = secondCar.getPersonList().size() - 1;
secondCar.getPersonList().get(lastIndex).setName("常連依頼客");
secondCar.getPersonList().add(ran);
secondCar.getPersonList().add(konan);
System.out.println("■2番目の車の乗員を変更した後の状態確認");
printCar(firstCar, secondCar);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
private static void printCar(Car firstCar, Car secondCar) {
System.out.println("・1番目の車");
System.out.println(firstCar.getName());
System.out.println(firstCar.getPrice() + "万円");
System.out.println("乗員:");
for (Person person : firstCar.getPersonList()) {
System.out.println(" " + person.getName());
}
System.out.println(""); // 改行
System.out.println("・2番目の車");
System.out.println(secondCar.getName());
System.out.println(secondCar.getPrice() + "万円");
System.out.println("乗員:");
for (Person person : secondCar.getPersonList()) {
System.out.println(" " + person.getName());
}
System.out.println(""); // 改行
}
}
処理としてはまずCarクラスのインスタンスfirstCarを作成してメンバの名前、価格、乗員の設定を行います。
その後、firstCarのインスタンスをcloneしてsecondCarのインスタンスを作成します。
そしてsecondCarのメンバの名前、価格、乗員を設定してもfirstCarのインスタンスには影響ないことを確認します。
動作確認
以下、実行結果のコンソール出力内容です。
重要なのは「1番目の車」と表記されているfirstCarインスタンスの各メンバの値が「■clone()直後の状態確認」と「■2番目の車の乗員を変更した後の状態確認」の時に変わっていないことです。
つまりディープコピーに成功している。ということになります。
以上となります。
ここまでお読みいただきありがとうございました。