深拷贝(clone)、浅拷贝
简易理解:
浅拷贝:只拷贝源对象的地址,所以源对象的任何值变化,拷贝对象值也随着变化。
深拷贝:拷贝对象的值,而不是地址,当源对象的值发生变化,拷贝对象的值不会发生变化
文章目录
浅拷贝
例子展示:
先建立一个对象
可以看见,浅拷贝改变源对象的值,拷贝对象值也会发生改变
深拷贝
为啥我们需要深拷贝呢?在某些业务场景下,我们需要完全相同但是缺不相关联的对象。其实大体有两种,Serializable与Cloneable
深拷贝的几种方式:
- 构造函数方式
- 重写clone方法
- Apache Commons Lang 序列化
- Gson序列化
- 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
在同一个 包中:
- 在(子类[允许被继承]、父类)可直接使用
子类:
子类的mian方法也可以访问
父类:
- 子类外,可以通过(子类、父类)【对象 .】
不在同一个包中:
- 当前类外,不能使用 **【对象 .】**的方式。
可见到没有 A03 类中的 a03 成员
- 子类中,可继承protected成员,且在非main方法中可直接使用名称进行访问。在子类的main方法中可(子类) **【对象.】**调用,但是不能(父类)调用,这一点有点像 序号1的内容(相当于父类跨包)。
- 子类外,与子类同包。通过子类打点方式调用这个方式调用protected是不行的,更别说父类了。需要重写这个成员或方法。
来重新加上,这样肯定就可以了,相当于之前的同包
4.在使用子类的类中,与父类同包,(子类,父类)都可以通过打点的方式获得
以上结果需把之前重写的注释掉
当然这是把A02之前覆盖A03的procted删去了的,不然又是不同包访问了某个类类中的procted了,会报错。这是不同包中 1 的结论
为什么colone要重写且改成public
-
与子类与父类都不同包,若想通过子类访问父类的procted,则需要重写该成员并将访问修饰符改为 public。 !!!!!说了那么多,其实这才是跟我们要讲的clone相关的,前面只是带大家复习一下
为什么呢?前面不是说了吗,不同包不能打点使用procted,那就只能比他上一级咯,private->default->procted->public。但是clone()是Object的,我们继承过来的,想想 我们上面的例子 **不同包中的第三点,与子类同包,与子类不同类调用子类打点的方式需要重写这个protected。因为不然相当于父类的成员跨包,**现在在前面的基础上改成了与子类不同包了,类比一下肯定是要重写方法的,好像感觉难度也增加了。那么现在有第三个包了,我们不可能在第三个包又加这个子类吧,所以我们就可以把子类重写的procted访问修饰符改为public就可以了。
当然这个可以帮助我们达到第三个包访问的目的,但是访问修饰符改变了,这个需要我们自己去权衡。
在开发中这种情况很常见。往往我们的一个对象(默认继承了java.lang.Object)在一个entity包下,然后又在另一个包下使用这个bean,这样就是三个包了
示例:(三个不同包下)
会报错,此时将A02中的protected改为public。结果显而易见
clone()方法
但是,我们发现地址不一样。成功。看起来比两个new简洁。
如果Person有多个属性,通过new的方式看起来是不是很恶心:
后期修改就比较麻烦。
但是呢,它不能完成成员中有对象的clone除了【String】,否则对象就是浅拷贝,这是他的弊端就凸显出来了。下面举例:
当clone()对象的成员中存在一个对象时
准备A02【包含对象A01】:
A01:
结果:
可以发现,clone是clone了,但是里面的对象是浅拷贝。修改clone的也修改了原来的。那么在这种情况下怎么进行深拷贝呢?
那么我们想想是不是应该把A01也就是它的这个成员对象也变为clone呢?试一试。
可以看见,还是不行。类比这么想一下,我们之前A02重写了clone方法,在Test中克隆的,显式调用了clone,所以才深拷贝了A02。
现在A02中有A01对象了,是也要对A01进行深拷贝的,是不是也该在A02中的clone方法显式调用一下呢?
结果:
至于为什么这么做,我还不能做出很好的解释[之后补上]。不过通过这样的例子你应该可以很好地快速记忆,知道如何使用了。
那么如果没有实现Cloneable接口,重写了clone方法呢?
会报 CloneNotSupportedException异常
如果没重写clone()方法,实现了Cloneable接口呢?。。。那肯定没clone方法啊。其实我还是觉得clone()比较麻烦,使用起来要多加小心,例如你必须对所有的对象都要有所了解。那么有没有其他方式呢?其实也就是Serializable,现在有很多框架都对序列化进行了封装,方便我们使用。
如果对象很复杂,多个继承怎么办,用clone就比较麻烦了。
可以考虑通过序列化的方式。
本篇文章不讲框架的使用,主要是帮助大家理解深拷贝是什么东西?
至于序列化与框架使用,之后章节详细讲解。