【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番目の車の乗員を変更した後の状態確認」の時に変わっていないことです。

つまりディープコピーに成功している。ということになります。


以上となります。

ここまでお読みいただきありがとうございました。

Java

Posted by だゆう