java的克隆机制
Java中对象的创建
使用new操作符创建一个对象
new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
使用clone方法复制一个对象
clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
使用序列化的方式克隆一个对象
把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。
Clone()方法的延伸思考
复制对象 or 复制引用
|
|
有结果可看出,p1只是复制引用,p2才是复制对象。
string 在clone()中的特殊性
我们来分析一下上面的String类型的拷贝。
首先string源码分析如下地址:
https://luckylau.github.io/2017/05/19/%E4%BD%A0%E6%87%82java%E5%90%97-7/
String不是基本数据类型,但是在深复制的时候并没有进行单独的复制,也就是说违反了深复制,仅仅复制了引用,而String没有实现cloneable接口,也就是说只能复制引用。
在修改克隆之后的对象之后,会不会将原来的值也改变了?
当然是NO。因为String是在内存中不可以被改变的对象,所以克隆相当于1个String内存空间有两个引用,当修改其中的一个值的时候,会新分配一块内存用来保存新的值,这个引用指向新的内存空间,原来的String因为还存在指向他的引用,所以不会被回收,这样,虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值。
String对象真的不可变吗?
用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。
|
|
如何实现深拷贝
如果在拷贝一个对象时,要想让这个拷贝的对象和源对象完全彼此独立,那么在引用链上的每一级对象都要实现Cloneable接口来被显式的拷贝。下面的实例就是完全深拷贝。
|
|
假如face不实现Cloneable,虽然对Body对象内所引用的其他对象(目前只有Head)都进行了拷贝,也就是说两个独立的Body对象内的head引用已经指向了独立的两个Head对象。但是,这对于两个Head对象来说,他们指向了同一个Face对象,是一种不彻底的拷贝。
但是实现完全深拷贝在引用关系非常复杂的情况下, 是很困难的,比如某一级上引用了一个第三方的对象, 而这个对象没有实现clone方法, 那么在它之后的所有引用的对象都是被共享的。 假如下面实例被Head引用的Face类是第三方库中的类,并且没有实现Cloneable接口,那么在Face之后的所有对象都会被拷贝前后的两个Body对象共同引用。假设Face对象内部组合了Mouth对象,并且Mouth对象内部组合了Tooth对象, 内存结构如下图:
序列化方法的延伸思考
|
|
参考:
http://blog.csdn.net/zhangjg_blog/article/details/18369201
http://www.cnblogs.com/carbs/archive/2012/06/26/2564373.html