Android冷知识——Java中的克隆
对象的创建
在android中,对象的创建分为两种形式,一种是使用new操作符创建对象,另一种是调用clone方法复制对象。这两个操作在使用上有以下的区别:
使用new操作符创建对象:对new的对象分配内存,调用其构造方法,并将创建好的对象引用发布到外部 调用clone方法复制对象:对clone的对象分配内存,对新分配的内存域使用原对象进行填充,并将clone的对象引用发布到外部克隆的使用
在对象中可以使用clone(),必须实现cloneable接口,复写clone方法,外部才可以调用clone()
public class person implements cloneable{ public string name; public int age; @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
复制对象和复制引用
一、复制对象
person p1 = new person(); person p2 = p1; log.e("tag", p1.tostring()); log.e("tag", p2.tostring());
通过输出的结果看出,两个对象的地址是相同的,所以这两个对象是同一个对象,这种现象叫做对象的复制
com.hensen.fashionsource.person@a6ea782 com.hensen.fashionsource.person@a6ea782
二、复制引用
try { person p1 = new person(); person p2 = (person) p1.clone(); log.e("tag", p1.tostring()); log.e("tag", p2.tostring()); } catch (clonenotsupportedexception e) { e.printstacktrace(); }
通过输出的结果看出,两个对象的地址不相同,所以这两个对象是不同对象,这种现象叫做引用的复制
com.hensen.fashionsource.person@38b31e93 com.hensen.fashionsource.person@3046fed0
浅拷贝和深拷贝
使用clone()的过程中,clone会遇到浅拷贝和深拷贝的问题,下面是浅拷贝和深拷贝的概念介绍:
浅拷贝:将对象中的所有字段复制到新的对象中。其中,基本数据类型的值被复制到对象中后,在对象中的修改不会影响到源对象对应的值。而引用类型的值被复制到对象中还是引用类型的引用,而不是引用的对象,在对象中对引用类型的字段值做修改会影响到源对象本身。简单的说,只拷贝基本数据类型,引用类型不会被拷贝。 深拷贝:将对象中的所有字段复制到新的对象中。不过,无论是对象的基本数据类型,还是引用类型,都会被重新创建并赋值,对于新对象的修改,不会影响到源对象本身。简单的说,拷贝出完全相同的对象,对新对象的修改和源对象没有任何关系。clone()属于浅拷贝,但是也可以将clone()方法转换成深拷贝的处理,下面开始介绍
一、string类型的特殊性
由于clone()属于浅拷贝,那么在上面的例子中,按理说,person对象的name字段,string类型不是基本数据类型,且string没有实现cloneable接口,在这里调用clone()仅仅是复制了string引用,那么克隆之后的对象修改name字段的值会不会将原来的值也改变了?下面我们通过例子验证
try { person p1 = new person(); person p2 = (person) p1.clone(); p1.name = "aaa"; p2.name = "bbb"; log.e("tag", p1.name.tostring()); //aaa log.e("tag", p2.name.tostring()); //bbb } catch (clonenotsupportedexception e) { e.printstacktrace(); }
这个例子中我们期待的输出应该都是bbb,实际上输出的结果是aaa和bbb。这是因为string类型被final修饰符修饰,在内存中是不可以被改变的对象,每次对新的字符串赋值都会分配一块新内存,并指向它。所以string类型在clone中属于特殊情况。
二、浅拷贝
回归真题,clone()属于浅拷贝,那么怎么去验证它呢?下面我们对person增加hand(手)引用类型,然后再hand(手)中又增加finger(手指)引用类型,代码如下
public class person implements cloneable{ public string name; public int age; public hand hand; public person(hand hand) { this.hand = hand; } @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
public class hand { public finger finger; public hand(finger finger) { this.finger = finger; } }
public class finger { }
我们通过比较person里和hand里和finger里的引用类型的地址是否相同,可以看出clone()的本质是浅拷贝
try { person p1 = new person(new hand(new finger())); person p2 = (person) p1.clone(); log.e("tag", "" + p1.hand); log.e("tag", "" + p2.hand); log.e("tag", "" + p1.hand.finger); log.e("tag", "" + p2.hand.finger); } catch (clonenotsupportedexception e) { e.printstacktrace(); }
输出结果可以看出,clone()引用类型的地址是相同的
com.hensen.fashionsource.hand@38b31e93 com.hensen.fashionsource.hand@38b31e93 com.hensen.fashionsource.finger@3046fed0 com.hensen.fashionsource.finger@3046fed0
三、深拷贝
如果我们想对person对象进行深拷贝该怎么做呢?我们可以让person的引用hand具有拷贝的功能,那么很自然的可以在person拷贝的时候拷贝一份hand,所以hand我们对hand实现cloneable接口,让其具备克隆功能。
public class person implements cloneable{ public string name; public int age; public hand hand; public person(hand hand) { this.hand = hand; } @override protected object clone() throws clonenotsupportedexception { person newper = (person) super.clone(); newper.hand = (hand) hand.clone(); return newper; } } public class hand implements cloneable{ public finger finger; public hand(finger finger) { this.finger = finger; } @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
下面我们继续输出拷贝前p1的地址和拷贝后p2的地址,我们可以发现hand已经完成了深拷贝了
com.hensen.fashionsource.hand@38b31e93 com.hensen.fashionsource.hand@3046fed0 com.hensen.fashionsource.finger@14be0ec9 com.hensen.fashionsource.finger@14be0ec9
接着,就剩下finger类了,同样我们重复上面的步骤即可,对finder类和hand类改造
public class hand implements cloneable{ public finger finger; public hand(finger finger) { this.finger = finger; } @override protected object clone() throws clonenotsupportedexception { hand newhand = (hand) super.clone(); newhand.finger = (finger) finger.clone(); return newhand; } } public class finger implements cloneable{ @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
下面我们继续输出拷贝前p1的地址和拷贝后p2的地址,我们可以发现finger已经完成了深拷贝了
com.hensen.fashionsource.hand@38b31e93 com.hensen.fashionsource.hand@3046fed0 com.hensen.fashionsource.finger@14be0ec9 com.hensen.fashionsource.finger@35e3b9ce
总结
至此,person对象已经完成了完整的深拷贝,这里简单的总结上面的所有内容
clone的使用必须实现cloneable的接口 clone属于浅拷贝,当可以通过复写clone()实现深拷贝 clone对string类型的拷贝具有特殊性。