细说 Java 的深拷贝和浅拷贝

版权声明:spa

本帐号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影全部。code

未经容许,不得转载。对象

1、前言

任何变成语言中,其实都有浅拷贝和深拷贝的概念,Java 中也不例外。在对一个现有的对象进行拷贝操做的时候,是有浅拷贝和深拷贝之分的,他们在实际使用中,区别很大,若是对其进行混淆,可能会引起一些难以排查的问题。继承

本文就在 Java 中的深拷贝和浅拷贝作一个详细的解说。接口

2、什么是浅拷贝和深拷贝

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

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

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

因此到如今,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对 类的实例对象 这种引用数据类型的不一样操做而已。it

总结来讲:io

一、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

/clone-qian.png

二、深拷贝:对基本数据类型进行值传递,对引用数据类型,建立一个新的对象,并复制其内容,此为深拷贝。

/clone-深.png

3、Java 中的 clone()

3.1 Object 上的 clone() 方法

在 Java 中,全部的 Class 都继承自 Object ,而在 Object 上,存在一个 clone() 方法,它被声明为了 protected ,因此咱们能够在其子类中,使用它。

而不管是浅拷贝仍是深拷贝,都须要实现 clone() 方法,来完成操做。

/clone-method.png

能够看到,它的实现很是的简单,它限制全部调用 clone() 方法的对象,都必须实现 Cloneable 接口,否者将抛出 CloneNotSupportedException 这个异常。最终会调用 internalClone() 方法来完成具体的操做。而 internalClone() 方法,实则是一个 native 的方法。对此咱们就不必深究了,只须要知道它能够 clone() 一个对象获得一个新的对象实例便可。

/clone-cloneable.png

而反观 Cloneable 接口,能够看到它其实什么方法都不须要实现。对他能够简单的理解只是一个标记,是开发者容许这个对象被拷贝。

3.2 浅拷贝

先来看看浅拷贝的例子。

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

/clone-father01.png

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

/clone-Demo1.png

最后看看输出的 Log :

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 560973324
I/cxmyDev: fatherB hash : 560938740
I/cxmyDev: fatherA name : 张三
I/cxmyDev: fatherB name : 张三

能够看到,使用 clone() 方法,从 ==hashCode 的不一样能够看出,clone() 方法实则是真的建立了一个新的对象。

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

来验证这一点,继续看下去,在 FatherClass 中,还有一个 ChildClass 的对象 child ,clone() 方法是否也能够正常复制它呢?改写一个上面的 Demo。

/clone-Demo2.png

看到,这里将其内的 child 进行负责,用起来看看输出的 Log 效果。

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 560975188
I/cxmyDev: fatherB hash : 560872384
I/cxmyDev: fatherA name : 张三
I/cxmyDev: fatherB name : 张三
I/cxmyDev: ==================
I/cxmyDev: A.child == B.child : true
I/cxmyDev: fatherA.child hash : 560891436
I/cxmyDev: fatherB.child hash : 560891436

从最后对 child 的输出能够看到,A 和 B 的 child 对象,实际上仍是指向了统一个对象,只对对它的引用进行了传递。

3.3 深拷贝

既然已经了解了对 clone() 方法,只能对当前对象进行浅拷贝,引用类型依然是在传递引用。

那么,如何进行一个深拷贝呢?

比较经常使用的方案有两种:

  1. 序列化(serialization)这个对象,再反序列化回来,就能够获得这个新的对象,无非就是序列化的规则须要咱们本身来写。
  2. 继续利用 clone() 方法,既然 clone() 方法,是咱们来重写的,实际上咱们能够对其内的引用类型的变量,再进行一次 clone()。

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

/clone-child1.png

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

再来看看输出的 Log。

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 561056732
I/cxmyDev: fatherB hash : 561057344
I/cxmyDev: fatherA name : 张三
I/cxmyDev: fatherB name : 张三
I/cxmyDev: ==================
I/cxmyDev: A.child == B.child : false
I/cxmyDev: fatherA.child hash : 561057304
I/cxmyDev: fatherB.child hash : 561057360

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

其实深拷贝的思路都差很少,序列化也好,使用 clone() 也好,实际上都是须要咱们本身来编写拷贝的规则,最终实现深拷贝的目的。

若是想要实现深拷贝,推荐使用 clone() 方法,这样只须要每一个类本身维护本身便可,而无需关心内部其余的对象中,其余的参数是否也须要 clone() 。

4、总结

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

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

公众号二维码.jpg