Javaにおけるオブジェクトコピーの種類と実装方法

オブジェクトコピーの基本的な概念

Javaプログラミングにおいてオブジェクトの複製を作成する際、いくつかのコピー方式が存在します。単純な代入操作から始まり、より複雑なコピー手法まで、それぞれの特性を理解することが重要です。

1. 参照のコピー

最も基本的なコピー形式は参照のコピーです。これは単純な代入操作によって実現されます。 Personクラスの定義:

public class Person {
    private String fullName;
    private int birthYear;
    
    public Person() {}
    
    public String getFullName() {
        return fullName;
    }
    
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }
    
    public int getBirthYear() {
        return birthYear;
    }
    
    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }
}
参照コピーのテスト:

public class CopyDemo {
    public static void main(String[] args) {
        Person personA = new Person();
        personA.setFullName("田中太郎");
        personA.setBirthYear(1990);
        
        Person personB = personA;
        
        System.out.println("personA: " + personA);
        System.out.println("personB: " + personB);
        System.out.println("同一性: " + (personA == personB));
    }
}
実行結果から、両方の変数が同じメモリ位置を指していることが確認できます。これは実際には新しいオブジェクトを作成せず、既存のオブジェクトへの参照を共有しているだけです。

2. オブジェクトのコピー

オブジェクトのコピーでは、新しいオブジェクトがヒープメモリ上に生成されます。これを実現するには、クラスがCloneableインタフェースを実装し、clone()メソッドをオーバーライドする必要があります。 Personクラスの修正:

public class Person implements Cloneable {
    private String fullName;
    private int birthYear;
    
    // ゲッターとセッターは省略
    
    @Override
    public Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
オブジェクトコピーのテスト:

public class CopyDemo {
    public static void main(String[] args) {
        Person original = new Person();
        original.setFullName("佐藤花子");
        original.setBirthYear(1985);
        
        Person duplicate = original.clone();
        duplicate.setFullName("鈴木一郎");
        
        System.out.println("元のオブジェクト: " + original);
        System.out.println("複製されたオブジェクト: " + duplicate);
        System.out.println("同一性チェック: " + (original == duplicate));
    }
}
実行結果により、二つの異なるオブジェクトが生成されたことが確認できます。

シャローコピーとディープコピーの詳細

オブジェクトコピーには、シャローコピーとディープコピーの二つのアプローチが存在します。主な違いは、オブジェクトが他のオブジェクトを参照している場合の振る舞いにあります。

1. シャローコピーの実装

シャローコピーでは、オブジェクトのフィールドはコピーされますが、参照先のオブジェクトは共有されます。 Departmentクラス(参照先オブジェクト):

public class Department implements Cloneable {
    private String departmentName;
    private String location;
    
    public Department(String name, String loc) {
        this.departmentName = name;
        this.location = loc;
    }
    
    public String getDepartmentName() {
        return departmentName;
    }
    
    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }
    
    @Override
    public String toString() {
        return "Department{name='" + departmentName + "', location='" + location + "'}";
    }
    
    @Override
    protected Department clone() {
        try {
            return (Department) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
Employeeクラス(コピー対象オブジェクト):

public class Employee implements Cloneable {
    private String employeeId;
    private String name;
    private Department department;
    
    public Employee(String id, String name, Department dept) {
        this.employeeId = id;
        this.name = name;
        this.department = dept;
    }
    
    @Override
    public Employee clone() {
        try {
            return (Employee) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    
    @Override
    public String toString() {
        return "Employee{id='" + employeeId + "', name='" + name + "', department=" + department + "}";
    }
}
シャローコピーの検証:

public class CopyComparison {
    public static void main(String[] args) {
        Department salesDept = new Department("営業部", "東京");
        Employee emp1 = new Employee("E001", "山田太郎", salesDept);
        
        Employee emp2 = emp1.clone();
        emp2.setName("佐藤次郎");
        
        salesDept.setDepartmentName("海外営業部");
        
        System.out.println("元の従業員: " + emp1);
        System.out.println("複製された従業員: " + emp2);
        System.out.println("部署オブジェクトの比較: " + (emp1.getDepartment() == emp2.getDepartment()));
    }
}

2. ディープコピーの実装

ディープコピーでは、参照先のオブジェクトも含めてすべてが複製されます。 Employeeクラスのclone()メソッドを修正:

@Override
public Employee clone() {
    try {
        Employee cloned = (Employee) super.clone();
        cloned.department = this.department.clone();
        return cloned;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
ディープコピーの検証:

public class CopyComparison {
    public static void main(String[] args) {
        Department itDept = new Department("IT部", "大阪");
        Employee original = new Employee("E002", "鈴木三郎", itDept);
        
        Employee copy = original.clone();
        copy.setName("高田四郎");
        
        itDept.setDepartmentName("開発部");
        
        System.out.println("元の従業員情報: " + original);
        System.out.println("複製された従業員情報: " + copy);
        System.out.println("部署オブジェクトの同一性: " + (original.getDepartment() == copy.getDepartment()));
    }
}
実行結果から、ディープコピーでは元のオブジェクトと複製されたオブジェクトが完全に独立していることが確認できます。参照先のDepartmentオブジェクトも個別に複製されているため、一方の変更がもう一方に影響を与えることはありません。

コピー方式の選択基準

どのコピー方式を選択するかは、具体的な要件に依存します。 ・参照コピーは、単に同じオブジェクトを複数の変数から操作したい場合に適しています。 ・シャローコピーは、オブジェクトのトップレベルのフィールドのみを独立させたい場合に有用です。 ・ディープコピーは、オブジェクトグラフ全体を完全に独立させたい場合に必須です。 パフォーマンスの観点から、シャローコピーはディープコピーより高速ですが、オブジェクト間の予期せぬ相互作用を引き起こす可能性があります。一方、ディープコピーはより多くのメモリを消費し、実行に時間がかかる場合がありますが、オブジェクトの完全な独立性を保証します。

5月18日 22:42 投稿