深拷贝(clone)、浅拷贝

深拷贝(clone)、浅拷贝

简易理解:

浅拷贝:只拷贝源对象的地址,所以源对象的任何值变化,拷贝对象值也随着变化。

深拷贝:拷贝对象的值,而不是地址,当源对象的值发生变化,拷贝对象的值不会发生变化

浅拷贝

例子展示:

先建立一个对象
在这里插入图片描述
在这里插入图片描述
可以看见,浅拷贝改变源对象的值,拷贝对象值也会发生改变

深拷贝

为啥我们需要深拷贝呢?在某些业务场景下,我们需要完全相同但是缺不相关联的对象。其实大体有两种,Serializable与Cloneable

深拷贝的几种方式:

  1. 构造函数方式
  2. 重写clone方法
  3. Apache Commons Lang 序列化
  4. Gson序列化
  5. Jackson序列化

构造函数完成深拷贝

在这里插入图片描述
这是通过new的方式,对系统的开销比较大。它俩是隔离的

重写clone()方法

它是Object中的。这是一个native方法,即用非java语言编写的,从一方面来将它的效率更高。

在这里插入图片描述
可以看见它是 protected修饰的,该修饰符的访问范围。复习一下

访问级别 访问控制符 同类 同包 子类 不同包
公开 public true true true ture
受保护 protected true true true false
默认 没有修饰符 true true false false
私有 private true false false false

所以要想克隆对象,要实现Cloneable接口。即告诉虚拟机我们可以利用这个对象完成深拷贝
在这里插入图片描述
可以看见它是没有任何抽象方法的

在这里插入图片描述
可以看见,我们现在还不能clone,还需要重写方法。因为Cloneable它仅仅是一个标志,你可以看见,它里面是空的。

来实现Object的clone方法
在这里插入图片描述

但是,我们还需要修改三个地方。访问修饰符和返回对象的类型
在这里插入图片描述

protected解释,例子

  • 这里访问修饰符由 procted改为了 public。这里复习一下java基础:子类重写父类的方法时访问修饰符要大于等于父类的,否则就破坏了继承规则:本来父类的某个方法是可以访问的,到了子类这里就不可以访问了。

具体一点,procted是访问修饰符里面最复杂的

准备一个 java0包中的A03,A02,java包中的A01与A02

image-20200609124919263

在同一个 包中:
  1. 在(子类[允许被继承]、父类)可直接使用

子类:

image-20200609125336319

子类的mian方法也可以访问

image-20200609131751536

父类:
image-20200609125401692

  1. 子类外,可以通过(子类、父类)【对象 .】

image-20200609125806052

不在同一个包中:
  1. 当前类外,不能使用 **【对象 .】**的方式。

image-20200609124734019

可见到没有 A03 类中的 a03 成员

  1. 子类中,可继承protected成员,且在非main方法中可直接使用名称进行访问。在子类的main方法中可(子类) **【对象.】**调用,但是不能(父类)调用,这一点有点像 序号1的内容(相当于父类跨包)。

image-20200609132039340

  1. 子类外,与子类同包。通过子类打点方式调用这个方式调用protected是不行的,更别说父类了。需要重写这个成员或方法。

image-20200609132728703

来重新加上,这样肯定就可以了,相当于之前的同包

image-20200609132805755

image-20200609132857896

4.在使用子类的类中,与父类同包,(子类,父类)都可以通过打点的方式获得

image-20200609133128516

以上结果需把之前重写的注释掉

image-20200609133236547

当然这是把A02之前覆盖A03的procted删去了的,不然又是不同包访问了某个类类中的procted了,会报错。这是不同包中 1 的结论

为什么colone要重写且改成public
  1. 与子类与父类都不同包,若想通过子类访问父类的procted,则需要重写该成员并将访问修饰符改为 public!!!!!说了那么多,其实这才是跟我们要讲的clone相关的,前面只是带大家复习一下

    为什么呢?前面不是说了吗,不同包不能打点使用procted,那就只能比他上一级咯,private->default->procted->public。但是clone()是Object的,我们继承过来的,想想 我们上面的例子 **不同包中的第三点,与子类同包,与子类不同类调用子类打点的方式需要重写这个protected。因为不然相当于父类的成员跨包,**现在在前面的基础上改成了与子类不同包了,类比一下肯定是要重写方法的,好像感觉难度也增加了。那么现在有第三个包了,我们不可能在第三个包又加这个子类吧,所以我们就可以把子类重写的procted访问修饰符改为public就可以了。

    当然这个可以帮助我们达到第三个包访问的目的,但是访问修饰符改变了,这个需要我们自己去权衡。

    在开发中这种情况很常见。往往我们的一个对象(默认继承了java.lang.Object)在一个entity包下,然后又在另一个包下使用这个bean,这样就是三个包了

示例:(三个不同包下)
image-20200609135444883

image-20200609135503806

会报错,此时将A02中的protected改为public。结果显而易见

image-20200609135546245

clone()方法

在这里插入图片描述

但是,我们发现地址不一样。成功。看起来比两个new简洁。

如果Person有多个属性,通过new的方式看起来是不是很恶心:
在这里插入图片描述

后期修改就比较麻烦。

但是呢,它不能完成成员中有对象的clone除了【String】,否则对象就是浅拷贝,这是他的弊端就凸显出来了。下面举例:

当clone()对象的成员中存在一个对象时

准备A02【包含对象A01】:

image-20200610100800469

A01:

image-20200610100814682

结果:

image-20200610100929148

可以发现,clone是clone了,但是里面的对象是浅拷贝。修改clone的也修改了原来的。那么在这种情况下怎么进行深拷贝呢?

那么我们想想是不是应该把A01也就是它的这个成员对象也变为clone呢?试一试。

image-20200610101414241

可以看见,还是不行。类比这么想一下,我们之前A02重写了clone方法,在Test中克隆的,显式调用了clone,所以才深拷贝了A02。

现在A02中有A01对象了,是也要对A01进行深拷贝的,是不是也该在A02中的clone方法显式调用一下呢?

image-20200610112920610

结果:

image-20200610112953175

至于为什么这么做,我还不能做出很好的解释[之后补上]。不过通过这样的例子你应该可以很好地快速记忆,知道如何使用了。

那么如果没有实现Cloneable接口,重写了clone方法呢?

image-20200608211619116

会报 CloneNotSupportedException异常

如果没重写clone()方法,实现了Cloneable接口呢?。。。那肯定没clone方法啊。其实我还是觉得clone()比较麻烦,使用起来要多加小心,例如你必须对所有的对象都要有所了解。那么有没有其他方式呢?其实也就是Serializable,现在有很多框架都对序列化进行了封装,方便我们使用。

如果对象很复杂,多个继承怎么办,用clone就比较麻烦了。

可以考虑通过序列化的方式。

本篇文章不讲框架的使用,主要是帮助大家理解深拷贝是什么东西?

至于序列化与框架使用,之后章节详细讲解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章