Java为什么中的浅拷贝和深拷贝的区别

先说说拷贝的具体含义,拷贝就是把对象复制一份一模一样的。浅拷贝是只复制了对象和值,如果浅拷贝中有其他引用类型的对象,那就不会被拷贝,而是和拷贝源使用同一个对象,指向同一地址,此时其中一个对象修改子对象中的字段变量了,会影响到另一个对象。深拷贝是把子对象一起执行复制操作,如果其中还有更多一级的引用类型对象,也会被递归复制,而不是和源对象指向同一地址。

下面为了帮助理解两种拷贝的方式,用代码来演示一下:

浅拷贝

先定义两个类:Person和Pet,Person需要实现Cloneable接口并重写clone方法

public class Person implements Cloneable {

    private String name;
    private int age;
    private Pet pet;

    public Person(String name, int age, Pet pet) {
        this.name = name;
        this.age = age;
        this.pet = pet;
    }

    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 Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
    }
}

public class Pet {

    private String name;
    private int age;

    public Pet(String name, int age) {
        this.name = name;
        this.age = 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 "Pet{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试代码如下:

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Pet pet = new Pet("Dog mi", 2);
        Person person = new Person("Rosy", 24, pet);

        // 打印对象的地址
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getPet()));
        System.out.println(person);
        System.out.println("----------------------------------");

        Person clonePerson = person.clone();
        // 打印对象的地址,判断和原来的对象是否是同一个
        System.out.println(System.identityHashCode(clonePerson));
        System.out.println(System.identityHashCode(clonePerson.getPet()));
        System.out.println(clonePerson);

        System.out.println("***********************************");
        // 下面演示浅克隆出现的问题:此时想把克隆出来的对象的宠物修改名字为Cat uu
        clonePerson.getPet().setName("Cat uu");

        // 打印源对象和克隆后的对象查看效果
        System.out.println(person);
        System.out.println(clonePerson);
    }
}

效果如下:

1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
2093176254
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}

从测试代码中可以看到,Person对象的确被克隆了一份新的,但是它的引用对象pet仍然和原来的pet地址相同。最直观的就是,我们修改克隆出来的对象的宠物名称时,源对象的宠物名称也被修改了。这个问题在一些场合是致命的问题,会导致程序出现很严重的问题。因为你可能并不想修改某一个对象,但是修改另一个克隆对象的时候同时也导致该对象被修改。

所以,此时我们就需要使用到深克隆,把子对象也实现克隆,而不是指向原对象的地址。

深克隆

实现深克隆的方法一:手动实现子对象克隆

下面先修改一下Person和Pet类。Person需要在clone方法里调用Pet的clone方法,Pet需要实现Cloneable接口。

public class Person implements Cloneable {

    private String name;
    private int age;
    private Pet pet;

    public Person(String name, int age, Pet pet) {
        this.name = name;
        this.age = age;
        this.pet = pet;
    }

    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 Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person cloneObj = (Person) super.clone();
        // 克隆一个新的Pet对象,设定到新克隆的Person对象上
        cloneObj.setPet(this.getPet().clone());
        return cloneObj;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
    }
}
public class Pet implements Cloneable{

    private String name;
    private int age;

    public Pet(String name, int age) {
        this.name = name;
        this.age = 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
    protected Pet clone() throws CloneNotSupportedException {
        return (Pet) super.clone();
    }

    @Override
    public String toString() {
        return "Pet{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试代码不变:

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Pet pet = new Pet("Dog mi", 2);
        Person person = new Person("Rosy", 24, pet);

        // 打印对象的地址
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getPet()));
        System.out.println(person);
        System.out.println("----------------------------------");

        Person clonePerson = person.clone();
        // 打印对象的地址,判断和原来的对象是否是同一个
        System.out.println(System.identityHashCode(clonePerson));
        System.out.println(System.identityHashCode(clonePerson.getPet()));
        System.out.println(clonePerson);

        System.out.println("***********************************");
        // 下面尝试把克隆出来的对象的宠物修改名字为Cat uu
        clonePerson.getPet().setName("Cat uu");

        // 打印源对象和克隆后的对象查看效果
        System.out.println(person);
        System.out.println(clonePerson);
    }
}

执行效果如下:

1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
2093176254
1854731462
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}

从地址上来看,新Person和原Person已经克隆成功了,这点和浅克隆一致。但是浅克隆没有实现的Pet克隆在这里被实现了,而且修改名字为Cat uu的时候也并不会影响原来的对象,这就是深克隆,把对象全部克隆而并不是某些部分保留原来的引用地址。

实现深克隆的方式二:序列化和反序列化

下面使用的Person和Pet和浅克隆时的差不多,但是需要多实现一个接口:Serializable 。只有实现这个接口的对象才能够序列化,子对象是引用类型的也需要实现。

public class Person implements Cloneable, Serializable {

    private String name;
    private int age;
    private Pet pet;

    public Person(String name, int age, Pet pet) {
        this.name = name;
        this.age = age;
        this.pet = pet;
    }

    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 Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
    }
}

public class Pet implements Serializable {

    private String name;
    private int age;

    public Pet(String name, int age) {
        this.name = name;
        this.age = 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 "Pet{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

使用序列化和反序列化克隆对象:

import java.io.*;

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Pet pet = new Pet("Dog mi", 2);
        Person person = new Person("Rosy", 24, pet);

        // 打印对象的地址
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getPet()));
        System.out.println(person);
        System.out.println("----------------------------------");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("at0914a.txt")));){
            // 把person对象写入at0914.txt文件里
            oos.writeObject(person);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Person clonePerson = null;

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("at0914a.txt")));){
            // 把写入at0914.txt文件里的文本给反序列化成Person类型的对象
            clonePerson = (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        // 打印对象的地址,判断和原来的对象是否是同一个
        System.out.println(System.identityHashCode(clonePerson));
        System.out.println(System.identityHashCode(clonePerson.getPet()));
        System.out.println(clonePerson);

        System.out.println("***********************************");
        // 下面尝试把克隆出来的对象的宠物修改名字为Cat uu
        clonePerson.getPet().setName("Cat uu");

        // 打印源对象和克隆后的对象查看效果
        System.out.println(person);
        System.out.println(clonePerson);
    }
}

控制台输出效果如下:

1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
1490180672
460332449
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}

从地址和输出的对象属性来看,已经是成功设置深克隆了。

我们在使用克隆的时候,要尽量避免使用浅克隆,而是使用深克隆。除非你需要有一些特殊需求,比如说两个人养同一条宠物,那使用浅克隆也未尝不可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值