Java基础 | 一文搞懂浅拷贝与深拷贝

引用拷贝

在介绍浅拷贝与深拷贝之前,让我们区分一下引用拷贝和对象拷贝。顾名思义,引用拷贝会创建指向对象的引用变量的副本。

如果我们有一个 School1 对象,并且有一个指向它的school变量,并且我们创建了一个引用副本,那么我们现在将有两个School1变量,但仍然是一个对象。

class School1{
    public String name;
    public String address;

    public School1(String name, String address) {
        this.name = name;
        this.address = address;
    }

}

public class ReferenceCopyExample {
    public static void main(String[] args) {
        School1 school = new School1("北京大学", "北京");
        School1 otherSchool = school;

        System.out.println("[1]:" + school);
        System.out.println("[2]:" + otherSchool);
    }
}

//输出
[1]:copy.School1@1f17ae12
[2]:copy.School1@1f17ae12

由输出结果可以看出,school 和otherSchool中的地址值是相同的,那么它们肯定是指向了一个相同的对象(“北京大学”, “北京”),并没有创建新的School对象。这就叫做引用拷贝,内存图如下:

引用拷贝不属于对象拷贝,因为它并不会拷贝新的对象,下面介绍的浅拷贝和深拷贝则属于对象拷贝,在拷贝的过程中会实实在在的创建新对象。

对象拷贝

对象拷贝会创建对象本身的副本,为什么要复制一个对象呢?如果我们想修改或移动一个对象,同时仍然保留原始对象,就需要创建一个对象副本(也称为克隆或复制)。

对象拷贝又分为两种:浅拷贝和深拷贝。

浅拷贝

先介绍一点铺垫知识:Java中的数据类型分为基本数据类型和引用数据类型。

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。

对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象对应的成员变量。也就是说两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

也就是说对象的浅拷贝会复制“主”对象,但不复制内部对象。“内部对象”在原始对象及其副本之间共享。

通过重写clone()方法可以实现浅拷贝,Object类是类结构的根类,其中有一个方法为clone(),这个方法就是进行的浅拷贝。我们可以通过调用clone()方法来实现对象的浅拷贝。

但是需要注意:Object类的clone()是受保护的(被protected修饰),所以我们无法直接使用。我们的解决方法是:使用clone方法的类实现Cloneable接口,然后重写clone()方法,通过super.clone()调用Object类中的原clone方法。如果还是不太理解,没关系,相信下面的例子会帮助你理解浅拷贝这块的内容。

class Address{
    public String city;

    public Address(String city) {
        this.city = city;
    }
}

//1.实现Cloneable接口
class School2 implements Cloneable{
    public String name;
    public int age;
    public Address address;

    public School2(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    //重写clone方法,方法中通过super.clone()调用Object类中的原clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//浅拷贝测试
public class ShallowCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        School2 school = new School2("北京大学", 11, new Address("北京"));
        School2 otherSchool = (School2)school.clone();

        System.out.println("[1]:" + school);
        System.out.println("[2]:" + otherSchool);

        System.out.println();

        System.out.println("[3]:" + school.address);
        System.out.println("[4]:" + otherSchool.address);

        System.out.println();
        school.name = "上海交大";
        school.age = 66;
        school.address.city = "上海";
        System.out.println("[5]:" + otherSchool.name);
        System.out.println("[6]:" + otherSchool.age);
        System.out.println("[7]:" + otherSchool.address.city);
    }
}

//输出
[1]:copy.School3@4d405ef7
[2]:copy.School3@6193b845

[3]:copy.Address@2e817b38
[4]:copy.Address@2e817b38

[5]:北京大学
[6]:11
[7]:上海

现在来分析程序的运行结果:通过[1]和[2]的输出可知:对象school和otherSchool的地址不一样,也就是说浅拷贝复制出一份新的对象。

通过[3]和[4]的输出可知:school和otherSchool中的的引用类型变量address地址一样,说明浅拷贝只是将school中的成员address的引用值(内存地址)复制一份给otherSchool中的address,并不会为引用类型成员变量创建新的对象。

通过[5]、[6]和[7]的输出可知:对于基本类型和String类型的成员变量,修改源对象的该变量,不会影响拷贝后的对象值;对于引用类型的成员变量address,修改school的该成员变量值,拷贝后的对象otherSchool中该成员也变了,因为school和otherSchool中的address指向的是同一个Adrress对象。我们可以看对应的内存布局来理解:
浅拷贝之后,修改school中成员变量之前内存布局:

修改school中成员变量之后内存布局:

深拷贝

与浅拷贝对应的就是深拷贝了,其不同于浅拷贝的是:对于对象中引用类型的成员,在拷贝的时候也是实实在在创建了一份。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝把要复制的对象所引用的对象也都复制了一遍。

class Address1 implements Cloneable{
    public String city;

    public Address1(String city) {
        this.city = city;
    }

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

//2.School3实现Cloneable接口
class School3 implements Cloneable{
    public int age;
    public Address1 address;

    public School3(int age, Address1 address) {
        this.age = age;
        this.address = address;
    }

    public Address1 getAddress() {
        return address;
    }

    public void setAddress(Address1 address) {
        this.address = address;
    }

    //重写clone方法,方法中通过super.clone()调用Object类中的原clone方法
    @Override
    public Object clone() throws CloneNotSupportedException {
        School3 school3 = (School3)super.clone();
        school3.setAddress((Address1) school3.getAddress().clone());
        return school3;
    }
}

//深拷贝测试
public class DeepCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        School3 school = new School3(11, new Address1("北京"));
        School3 otherSchool = (School3)school.clone();

        System.out.println("[1]:" + school);
        System.out.println("[2]:" + otherSchool);

        System.out.println();

        System.out.println("[3]:" + school.address);
        System.out.println("[4]:" + otherSchool.address);

        System.out.println();
        school.age = 66;
        school.address.city = "上海";
        System.out.println("[5]:" + otherSchool.age);
        System.out.println("[6]:" + otherSchool.address.city);
    }
}

//输出
[1]:copy.School3@4d405ef7
[2]:copy.School3@6193b845

[3]:copy.Address1@2e817b38
[4]:copy.Address1@c4437c4

[5]:11
[6]:北京

现在来分析程序的运行结果:

通过[1]和[2]的输出可知:源对象和拷贝的对象 地址不一样,也就是说深拷贝复制出一份新的对象,这点和浅拷贝一样。通过[3]和[4]的输出可知:源对象和拷贝的对象中的的引用类型变量address地址也一样,说明深拷贝将引用类型的成员变量对象也 拷贝了一份,这是不同于浅拷贝的地方

。通过[5]和[6]的输出可知:school和otherSchool中的两个Address1类型引用指向的是两个对象,所以对school中的Address1对象修改不影响otherSchool对象中的Address1对象。

下面这个内存布局可以帮助我们理解。

深拷贝之后,修改school中成员变量之前内存布局:

修改school中成员变量之后内存布局:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值