Javaの偽克隆、浅克隆、深克隆

一、Javaの偽克隆
Javaでは、基本型は"="演算子でコピーが可能です。しかし、参照型は"="演算子だけでコピーできません。これはJavaのメモリ管理と関係しています。Javaはスタックで基本型と参照型を保存し、ヒープでオブジェクトを保存します。参照型に"="を使用すると、参照先のオブジェクトではなく参照をコピーします。したがって、2つの参照型は同じオブジェクトを指し、一方を変更すると他方も変更されます。
public class Employee {
    private String name;
    private int age;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    
    @Override
    public String toString() {
        return "名前:" + name + ", 年齢:" + age;
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("コピー前:");
        Employee emp1 = new Employee();
        emp1.setName("テスト1");
        emp1.setAge(20);
        System.out.println("emp1の情報:");
        System.out.println(emp1);
        
        System.out.println("コピー後:");
        Employee emp2 = emp1;
        emp2.setName("テスト2");
        emp2.setAge(30);
        System.out.println("emp2の情報:");
        System.out.println(emp2);
        System.out.println("emp1の情報:");
        System.out.println(emp1);
    }
}
出力結果:
コピー前: emp1の情報: 名前:テスト1, 年齢:20 コピー後: emp2の情報: 名前:テスト2, 年齢:30 emp1の情報: 名前:テスト2, 年齢:30
emp1とemp2は同じオブジェクトを指しているため、emp2を変更するとemp1も変更されます。これを偽克隆と呼びます。
二、浅克隆
JavaのObjectクラスにはclone()メソッドがあります。浅克隆は、オブジェクト内部の基本型や不可変参照型(例:String)のみをコピーし、可変参照型は同じ参照先を共有します。
public class Address implements Cloneable {
    private String country;
    private String province;
    private String city;
    
    public String getCountry() { return country; }
    public void setCountry(String country) { this.country = country; }
    public String getProvince() { return province; }
    public void setProvince(String province) { this.province = province; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    
    @Override
    public String toString() {
        return "国:" + country + ", 省:" + province + ", 市:" + city;
    }
    
    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class Employee implements Cloneable {
    private String name;
    private int age;
    private Address address;
    
    public Employee(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    
    @Override
    public String toString() {
        return "名前:" + name + ", 年齢:" + age + "\n" + "住所:" + address.toString();
    }
    
    @Override
    public Employee clone() {
        try {
            return (Employee) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("コピー前:");
        Address addr = new Address();
        addr.setCountry("日本");
        addr.setProvince("東京都");
        addr.setCity("渋谷区");
        
        Employee emp1 = new Employee("山田太郎", 30, addr);
        System.out.println("emp1の情報:");
        System.out.println(emp1);
        
        System.out.println("コピー後:");
        Employee emp2 = emp1.clone();
        emp2.setName("山田花子");
        emp2.setAge(25);
        emp2.getAddress().setCountry("日本");
        emp2.getAddress().setProvince("大阪府");
        emp2.getAddress().setCity("大阪市");
        
        System.out.println("emp2の情報:");
        System.out.println(emp2);
        System.out.println("emp1の情報:");
        System.out.println(emp1);
    }
}
出力結果:
コピー前: emp1の情報: 名前:山田太郎, 年齢:30 住所:国:日本, 省:東京都, 市:渋谷区 コピー後: emp2の情報: 名前:山田花子, 年齢:25 住所:国:日本, 省:大阪府, 市:大阪市 emp1の情報: 名前:山田太郎, 年齢:30 住所:国:日本, 省:大阪府, 市:大阪市
浅克隆の場合は、Addressオブジェクトが1つで共有されます。そのため、emp2の住所を変更するとemp1の住所も変更されます。
三、深克隆
深克隆は、参照先のオブジェクトもコピーします。これには以下の方法があります。
1. 各参照型クラスにclone()メソッドを実装し、コピー時に使用します。
public class Address implements Cloneable {
    private String country;
    private String province;
    private String city;
    
    public String getCountry() { return country; }
    public void setCountry(String country) { this.country = country; }
    public String getProvince() { return province; }
    public void setProvince(String province) { this.province = province; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    
    @Override
    public String toString() {
        return "国:" + country + ", 省:" + province + ", 市:" + city;
    }
    
    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class Employee implements Cloneable {
    private String name;
    private int age;
    private Address address;
    
    public Employee(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    
    @Override
    public String toString() {
        return "名前:" + name + ", 年齢:" + age + "\n" + "住所:" + address.toString();
    }
    
    @Override
    public Employee clone() {
        try {
            Employee emp = (Employee) super.clone();
            emp.address = (Address) this.address.clone();
            return emp;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}
2. シリアライズを使用した方法
深克隆のもう一つの方法は、オブジェクトをシリアル化し、ストリームから復元することです。この方法では、参照先のオブジェクトも完全にコピーされます。ただし、シリアル化可能なオブジェクトである必要があります。
import java.io.*;
import java.util.Arrays;

public class Address implements Serializable {
    private String country;
    private String province;
    private String city;
    
    // 省略:コンストラクタ、ゲッター、セッター、toString()メソッド
}

public class Employee implements Serializable {
    private String name;
    private int age;
    private Address address;
    
    public Employee(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    // 省略:ゲッター、セッター、toString()メソッド
    
    public Employee deepClone() {
        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 (Employee) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
深克隆の利点は、参照先のオブジェクトも完全にコピーされることです。しかし、参照先のオブジェクトが多ければ多いほど、処理が複雑になります。
深克隆の実装方法:
1. 各参照型クラスにclone()メソッドを実装します。
2. シリアライズを使用します。
3. Transientキーワードを使用して特定のフィールドを除外します。

タグ: Java 深克隆 浅克隆 シリアル化

9月3日 22:58 投稿