java的深拷贝与浅拷贝

什么是浅拷贝和深拷贝

首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。

在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 = 号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对 类的实例对象 这种引用数据类型的不同操作而已。

总结来说:

1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
在这里插入图片描述
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。在这里插入图片描述

Java 中的 clone()

Object 上的 clone() 方法

在 Java 中,所有的 Class 都继承自 Object ,而在 Object 上,存在一个 clone() 方法,它被声明为了 protected ,所以我们可以在其子类中,使用它。

而无论是浅拷贝还是深拷贝,都需要实现 clone() 方法,来完成操作。

它的实现非常的简单,它限制所有调用 clone() 方法的对象,都必须实现 Cloneable 接口,否者将抛出 CloneNotSupportedException 这个异常。最终会调用 internalClone() 方法来完成具体的操作。而 internalClone() 方法,实则是一个 native 的方法。对此我们就没必要深究了,只需要知道它可以 clone() 一个对象得到一个新的对象实例即可

Cloneable 接口,它什么方法都不需要实现。对他可以简单的理解只是一个标记,是开发者允许这个对象被拷贝

浅拷贝举例

首先创建一个 class 为 FatherClass ,对其实现 Cloneable 接口,并且重写 clone() 方法。

public class FatherClass implements Cloneable{
    public String name;
    public int age;
    public ChildClass child;

    @NonNull
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class ChildClass {
    public String name;
    public int age;
}

然后先正常 new 一个 FatherClass 对象,再使用 clone() 方法创建一个新的对象

FatherClass fatherClassA = new FatherClass();
        fatherClassA.name = "正常张三";
        fatherClassA.age = 30;
        fatherClassA.child = new ChildClass();
        fatherClassA.child.name = "法外狂徒张三";
        fatherClassA.child.age = 18;

        try {
            FatherClass fatherClassB = (FatherClass) fatherClassA.clone();
            Log.d("TTT", "fatherClassA == fatherClassB:" + (fatherClassA == fatherClassB));
            Log.d("TTT", "fatherClassA hash:" + fatherClassA.hashCode());
            Log.d("TTT", "fatherClassB hash:" + fatherClassB.hashCode());
            Log.d("TTT", "fatherClassA name:" + fatherClassA.name);
            Log.d("TTT", "fatherClassB name:" + fatherClassB.name);
            Log.d("TTT", "===============");
            Log.d("TTT", "A.child == B.child:" + (fatherClassA.child == fatherClassB.child));
            Log.d("TTT", "fatherA.child hash:" + fatherClassA.child.hashCode());
            Log.d("TTT", "fatherB.child hash:" + fatherClassB.child.hashCode());

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

最后看看输出的 Log :

fatherClassA == fatherClassB:false
fatherClassA hash:156329482
fatherClassB hash:175877755
fatherClassA name:正常张三
fatherClassB name:正常张三
===============
A.child == B.child:true
fatherA.child hash:66385048
fatherB.child hash:66385048

分割线之前,使用 clone() 方法,从 ==hashCode 的不同可以看出,clone() 方法实则是真的创建了一个新的对象。

但这只是一次浅拷贝的操作。

对 child 的输出可以看到,A 和 B 的 child 对象,实际上还是指向了统一个对象,只是对它的引用进行了传递,所以这是一次浅拷贝

深拷贝举例

那么,如何进行一个深拷贝呢?
比较常用的方案有两种:
1、序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。
2、继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。

继续改写上面的 Demo ,让 ChildClass 也实现 Cloneable 接口。

public class ChildClass implements Cloneable{
    public String name;
    public int age;

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

然后修改 FatherClass

public class FatherClass implements Cloneable{
    public String name;
    public int age;
    public ChildClass child;

    @NonNull
    @Override
    protected Object clone() throws CloneNotSupportedException {
        FatherClass fatherClass = (FatherClass)super.clone();
        fatherClass.child = (ChildClass)this.child.clone();
        return fatherClass;
    }
}

最重要的代码就在 FatherClass.clone() 中,它对其内的 child ,再进行了一次 clone() 操作。

再看log

fatherClassA == fatherClassB:false
fatherClassA hash:156329482
fatherClassB hash:175877755
fatherClassA name:正常张三
fatherClassB name:正常张三
===============
A.child == B.child:false
fatherA.child hash:66385048
fatherB.child hash:123905265

可以看到,对 child 也进行了一次拷贝,这实则是对 ChildClass 进行的浅拷贝,但是对于 FatherClass 而言,则是一次深拷贝。

其实深拷贝的思路都差不多,序列化也好,使用 clone() 也好,实际上都是需要我们自己来编写拷贝的规则,最终实现深拷贝的目的。

如果想要实现深拷贝,推荐使用 clone() 方法,这样只需要每个类自己维护自己即可,而无需关心内部其他的对象中,其他的参数是否也需要 clone() 。

总结

到现在基本上就已经梳理清楚,Java 中浅拷贝和深拷贝的概念了。

实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。

细说 Java 的深拷贝和浅拷贝

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值