浅いコピーと深いコピーの比較

JavaBeginner
オンラインで実践に進む

はじめに

シャローコピーでは、単に元のオブジェクトのフィールドの値を新しいオブジェクトにコピーします。シャローコピーでは、元のオブジェクトが他のクラスオブジェクトへの参照を持っている場合、そのオブジェクトの参照のみがクローンされます。したがって、この参照されたオブジェクトの変更は、元のオブジェクトとクローンされたオブジェクトの両方に反映されます。一方、ディープコピーでは、参照されたすべてのオブジェクトを含む新しいオブジェクトが作成され、クローンされたオブジェクトに対して行われた変更は元のオブジェクトに影響を与えません。

コピー対象のオブジェクトを作成する

まず、学生の GPA を格納するための GPA 型(ユーザ定義クラス)のメンバ変数を持つ Student クラスを作成します。

class GPA {
    int firstYear;
    int secondYear;

    public GPA(int firstYear, int secondYear) {
        this.firstYear = firstYear;
        this.secondYear = secondYear;
    }

    public int getFirstYear() {
        return this.firstYear;
    }

    public int getSecondYear() {
        return this.secondYear;
    }

    public void setFirstYear(int firstYear) {
        this.firstYear = firstYear;
    }

    public void setSecondYear(int secondYear) {
        this.secondYear = secondYear;
    }
}

class Student {
    private String name;
    private GPA gpa;

    public Student(String name, GPA gpa) {
        this.name = name;
        this.gpa = gpa;
    }

    public String getName() {
        return this.name;
    }

    public GPA getGpa() {
        return this.gpa;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setGpa(GPA gpa) {
        this.gpa = gpa;
    }
}

浅いコピーのための clone() メソッドの実装

シャローコピーを実装するには、Objectクラスが提供するclone()メソッドを使用します。clone()メソッドは保護メソッドであるため、Cloneableインターフェイスを実装する必要があります。シャロークローニングを行うには、Studentクラスで単にclone()メソッドを呼び出します。

class Student implements Cloneable {
    //...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

clone() メソッドを使用したディープコピーの実装

以下のようにclone()メソッドをオーバーライドすることで、ディープコピーを行うことができます。clone()メソッドでは、newキーワードを使用して新しいオブジェクトを作成し、元のオブジェクトの値を新しいオブジェクトに割り当てます。新しいオブジェクトを作成したら、プリミティブ型を含まないすべての参照クラスに対して新しいオブジェクトを作成し、元の参照ではなく新しい参照を割り当てます。

class Student implements Cloneable {
    //...

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student newStudent = (Student)super.clone();
        newStudent.gpa = (GPA)this.gpa.clone();
        return newStudent;
    }
}

class GPA implements Cloneable {
    //...

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

コピーコンストラクタの実装

ディープコピーを作成する別の方法は、コピーコンストラクタを使用することです。コピーコンストラクタは、同じクラスのオブジェクトを入力として受け取り、同じ値を持つが新しい参照を持つ新しいオブジェクトを返すコンストラクタです。Studentクラスのコピーコンストラクタを作成します。

class Student {
    //...

    public Student(Student student) {
        this(student.getName(), new GPA(student.getGpa().getFirstYear(), student.getGpa().getSecondYear()));
    }
}

シリアライゼーションの実装

直列化もディープコピーを作成する方法を提供します。オブジェクトを直列化してから逆直列化することで、実質的に新しい参照を持つが同じ値を持つ新しいオブジェクトを生成することができます。これを行うには、ディープコピーが必要なクラスでSerializableインターフェイスを実装する必要があります。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class GPA implements Serializable {
    //...
}

class Student implements Serializable {
    //...

    public Student deepCopyUsingSerialization() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            return (Student)ois.readObject();
        } catch(Exception e) {
            return null;
        }
    }
}

テスト

以下のコードに示すように、異なるディープコピーの方法をテストすることができます。

public static void main(String[] args) {
    GPA gpa = new GPA(7, 8);
    Student original = new Student("John", gpa);

    // シャローコピー
    Student shallowCopy = null;
    try {
        shallowCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // clone() を使用したディープコピー
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // コピーコンストラクタを使用したディープコピー
    Student constructorCopy = new Student(original);

    // 直列化を使用したディープコピー
    Student serializationCopy = original.deepCopyUsingSerialization();

    // ディープコピーのテスト
    System.out.println("元のオブジェクト:\t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("シャローコピー: \t\t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("クローンコピー: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("コピーコンストラクタコピー: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("直列化コピー: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\ncloneCopy の GPA フィールドを変更した後:");
    System.out.println("元のオブジェクト:\t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("クローンコピー: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

実行

以下のコマンドを実行した後:

javac Lab.java && java Lab

次の出力が表示されます:

元のオブジェクト:   7 8
シャローコピー:    7 8
クローンコピー:    7 8
コピーコンストラクタコピー:  7 8
直列化コピー:    7 8

cloneCopyのGPAフィールドを変更した後:
元のオブジェクト:   7 8
クローンコピー:    10 9

まとめ

この実験では、Java でオブジェクトのディープコピーを作成する方法を学びました。シャローコピーは、元のオブジェクトのフィールド値を単に新しいオブジェクトにコピーするのに対し、ディープコピーでは、含まれるすべてのオブジェクトに対して新しい個別の参照を持つ新しいオブジェクトが作成されます。clone() メソッド、コピーコンストラクタ、または直列化を使用することでディープコピーを作成できます。参照クラスを持つオブジェクトを操作する際に予期しない動作を回避するためには、ディープコピーを行うことが重要です。