深入理解Java拷贝

浅拷贝(Shallow Copy)


浅拷贝会创建一个对象,对于原始对象(被拷贝的对象)中的基本数据类型字段,浅拷贝会直接复制这些值,对于引用类型的字段,浅拷贝会复制这个引用类型字段的地址,而不是复制这个引用类型字段所指向的对象本身。这意味着,如果原始对象中的引用类型字段指向一个对象,那么浅拷贝后的新对象中的相同字段仍然指向同一个对象。

这种情况下,我们如果修改了新对象中的引用类型字段所指向的对象,那么原始对象中的相同字段也会受到影响,因为它们指向的是同一个对象。

举个例子:

package org.example;

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = (Person) p1.clone(); // 浅拷贝

        p2.setName("John");
        p2.getAddress().setCity("Shanghai"); // 修改 p2 的 address

        System.out.println("p1: " + p1);
        System.out.println("p2: " + p2);
    }
}

class Address implements Cloneable {
    private String city;
    public Address(String city) { this.city = city; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    @Override
    public String toString() { return "Address{city='" + city + "'}"; }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }
}

class Person implements Cloneable {
    private String name;
    private Address address;
    public Person(String name, Address address) {
        this.name = name; this.address = address;
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', address=" + address + '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 只做浅拷贝,address 仍然指向同一个对象
    }
}

深拷贝(Deep Copy)

深拷贝会拷贝所有的属性,包括基本类型和引用类型。对于引用类型的字段,深拷贝会创建一个新的对象,并将原始对象中的引用类型字段所指向的对象也进行拷贝。这意味着,深拷贝后的新对象中的引用类型字段指向的是一个全新的对象,而不是原始对象中的同一个对象。

(1)实现Cloneable并重写clone()方法

对于简单对象,可以让类实现Cloneable接口,并重写clone()方法,在其中手动递归地克隆引用类型字段:

package org.example;

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = (Person) p1.clone(); // 浅拷贝

        p2.setName("John");
        p2.getAddress().setCity("Shanghai"); // 修改 p2 的 address

        System.out.println("p1: " + p1);
        System.out.println("p2: " + p2);
    }
}

class Address implements Cloneable {
    private String city;
    public Address(String city) { this.city = city; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    @Override
    public String toString() { return "Address{city='" + city + "'}"; }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }
}

class Person implements Cloneable {
    private String name;
    private Address address;
    public Person(String name, Address address) {
        this.name = name; this.address = address;
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', address=" + address + '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person copy = (Person) super.clone();
        copy.address = (Address) this.address.clone();
        return copy; // 深拷贝
    }
}

(2)使用序列化和反序列化

package org.example;

import java.io.*;

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = deepCopy(p1);

        p2.setName("John");
        p2.getAddress().setCity("Shanghai"); // 修改 p2 的 address

        System.out.println("p1: " + p1);
        System.out.println("p2: " + p2);
    }
    public static <T extends Serializable> T deepCopy(T obj) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    }
}

class Address implements Serializable{
    private String city;
    public Address(String city) { this.city = city; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    @Override
    public String toString() { return "Address{city='" + city + "'}"; }

}

class Person implements Serializable{
    private String name;
    private Address address;
    public Person(String name, Address address) {
        this.name = name; this.address = address;
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', address=" + address + '}';
    }

}

注意点

对于集合类型的深拷贝,需要特别注意,因为集合中的元素可能是引用类型的对象。可以使用递归的方式来实现深拷贝,或者使用第三方库如Apache Commons Lang的SerializationUtils来简化操作。