克隆概念
Java一切皆對(duì)象,克隆就是對(duì)對(duì)象的克?。豢寺】赡苈?tīng)起來(lái)有點(diǎn)高級(jí),也可以為對(duì)象復(fù)制或者對(duì)象拷貝。
平時(shí)開(kāi)發(fā)中,什么時(shí)候需要用到對(duì)象復(fù)制呢?當(dāng)你有一個(gè)實(shí)體類,有很多屬性,并且很多屬性已經(jīng)賦了值,這個(gè)時(shí)候需要對(duì)這個(gè)對(duì)象進(jìn)行修改操作,但后面還會(huì)用到原來(lái)的值,這時(shí)就需要對(duì)象復(fù)制。
淺克隆
用代碼舉個(gè)栗子先:
public static class C implements Cloneable{
String name;
@Override
public String toString() {
return "C{" +
"name='" + name + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Test
public void test2() throws CloneNotSupportedException {
C c = new C();
c.name = "Cat";
System.out.println(c + " hashCode: " + c.hashCode());
System.out.println(c.name + " hashCode: " + c.name.hashCode());
C copy = (C) c.clone();
System.out.println(copy + " hashCode: " + copy.hashCode());
System.out.println(copy.name + " hashCode: " + copy.name.hashCode());
c.name = "Dog";
System.out.println(c + " hashCode: " + c.hashCode());
System.out.println(copy + " hashCode: " + copy.hashCode());
}
test2方法是個(gè)單元測(cè)試方法,運(yùn)行結(jié)果:
C{name='Cat'} hashCode: 458209687
Cat hashCode: 67510
C{name='Cat'} hashCode: 233530418
Cat hashCode: 67510
C{name='Dog'} hashCode: 458209687
C{name='Cat'} hashCode: 233530418
clone()是Object的方法, 子類需要實(shí)現(xiàn)Cloneable接口,不實(shí)現(xiàn)調(diào)用會(huì)拋CloneNotSupportedException異常。
首先我定義了一個(gè)類C, c有個(gè)成員變量name,是String類型的。test2方法里,先創(chuàng)建一個(gè)實(shí)例c,給實(shí)例的name賦值為Cat,接著打印c和c.name的hashCode; 然后用c克隆一個(gè)實(shí)例賦值給copy, 答應(yīng)copy和copy.name的hashCode,對(duì)比c和copy的hashCode, 發(fā)現(xiàn)c和copy的hashCode是不同的,說(shuō)明它倆指向的是不同的兩個(gè)實(shí)例,在堆內(nèi)存中是有2塊區(qū)域。對(duì)比c.name和copy.name,發(fā)現(xiàn)它倆的hashCode是相同的. 接著吧c.name重新賦值,賦值為Dog,再看打印的log,沒(méi)有影響到copy的里name屬性。
再看另外一個(gè)栗子:
public static class A implements Cloneable{
B b;
@Override
public String toString() {
return "A{" +
"b=" + b +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static class B implements Cloneable{
String name;
@Override
public String toString() {
return "B{" +
"name=" + name +
'}';
}
}
@Test
public void test() throws CloneNotSupportedException {
A a = new A();
B b = new B();
b.name = "Cat";
a.b = b;
System.out.println(a + " hashCode: " + a.hashCode());
System.out.println(a.b + " hashCode: " + a.b.hashCode());
A copy = (A) a.clone();
System.out.println(copy + " hashCode: " + copy.hashCode());
System.out.println(copy.b + " hashCode: " + copy.b.hashCode());
b.name = "Dog";
System.out.println(a + " hashCode: " + a.hashCode());
System.out.println(copy + " hashCode: " + copy.hashCode());
}
運(yùn)行的結(jié)果:
A{b=B{name=Cat}} hashCode: 458209687
B{name=Cat} hashCode: 233530418
A{b=B{name=Cat}} hashCode: 683287027
B{name=Cat} hashCode: 233530418
A{b=B{name=Dog}} hashCode: 458209687
A{b=B{name=Dog}} hashCode: 683287027
前一個(gè)例子中類C中有個(gè)String的成員變量, 而這個(gè)例子中類A中有個(gè)自定義的類B成員變量, 但你會(huì)發(fā)現(xiàn)創(chuàng)建A實(shí)例, 復(fù)制一個(gè)A實(shí)例后,修改a成員變量b的name為Dog, 看日志copy里b的name也跟著改變啦。還沒(méi)修改為Dog之前,a和copy的hashCode是不同的,說(shuō)明它倆指向的是不同的兩個(gè)實(shí)例,在堆內(nèi)存中是有2塊區(qū)域;a.b和copy.b的hashCode是相同的,它倆指向堆內(nèi)存中的同一區(qū)域,所以當(dāng)你修改a.b.name的值, copy.b.name肯定也跟著改變。
Object的clone方法只是淺克隆, 第一個(gè)例子中, c.name = "Dog" 等同于c.name = new String("Dog"),
c.name的引用指向已經(jīng)發(fā)生了改變, 這時(shí)c.name和copy.name它倆指向的是不同的兩個(gè)實(shí)例。第二個(gè)例子中,a.b和copy.b始終都是同一個(gè)引用,
如果改為 B b1 = new B(); a.b = b1; b1.name = "Dog", 這樣子就跟第一個(gè)例子相同。
淺克隆,對(duì)于被克隆的類中成員變量都是基本數(shù)據(jù)類型,可以實(shí)現(xiàn)了兩份數(shù)據(jù);被克隆的類中成員變量是對(duì)象類型,那么這個(gè)成員變量還是原來(lái)的引用,修改為新對(duì)象的值,舊對(duì)象的該對(duì)象類型的成員變量還是會(huì)變化。
深克隆
深克隆, 被克隆的類中成員變量無(wú)論是什么類型, 都可以實(shí)現(xiàn)了兩份數(shù)據(jù);
深克隆主要有兩個(gè)方式實(shí)現(xiàn):
- 重寫(xiě)clone方法
- 序列化與反序列化
重寫(xiě)clone方法
重寫(xiě)clone方法實(shí)現(xiàn)深克隆比較麻煩,要對(duì)所有是對(duì)象類型的成員變量,進(jìn)行重新創(chuàng)建實(shí)例,重新賦值;
如果集合類更麻煩,比如說(shuō)ArrayList<Model>, ArrayList的重寫(xiě)了clone(), 但還是淺克隆, 要實(shí)現(xiàn)深克隆需要遍歷所有Model,創(chuàng)建實(shí)例,重新賦值。
下面代碼是對(duì)上面淺克隆第二個(gè)兩字進(jìn)行了深克隆的實(shí)現(xiàn), 重寫(xiě)類A的clone方法:
public static class A implements Cloneable{
B b;
@Override
public String toString() {
return "A{" +
"b=" + b +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
A a = (A) super.clone();
B b = new B();
b.name = this.b.name;
a.b = b;
return a;
}
}
序列化與反序列化
序列化與反序列化實(shí)現(xiàn)深克隆方式很多,你可以將實(shí)例轉(zhuǎn)成json的字符串,再將字符串轉(zhuǎn)回實(shí)例,相當(dāng)于復(fù)制了一份;你也可以O(shè)bjectOutputStream和ObjectInputStream實(shí)現(xiàn); 在安卓上,可以利用Parcelable實(shí)現(xiàn)等等。
這種方式相對(duì)于重寫(xiě)clone方法實(shí)現(xiàn)可能會(huì)簡(jiǎn)單點(diǎn),但性能上會(huì)差很多。
下面就簡(jiǎn)單利用ObjectOutputStream和ObjectInputStream實(shí)現(xiàn),代碼如下:
static <T extends Serializable> T copy(T origin) {
ByteArrayInputStream in = null;
ByteArrayOutputStream pos = null;
T copyList = null;
try {
pos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(pos);
out.writeObject(origin);
in = new ByteArrayInputStream(pos.toByteArray());
ObjectInputStream oin = new ObjectInputStream(in);
copyList = (T) oin.readObject();
} catch (Exception ignored) {
}finally {
if(in != null){
try {
in.close();
} catch (IOException ignored) {
}
}
if(pos != null){
try {
pos.close();
} catch (IOException ignored) {
}
}
}
return copyList;
}