分析之前先了解兩個概念
1.淺拷貝
什麼是淺拷貝
淺拷貝是按位拷貝對象,它會建立一個新對象,這個對象有着原始對象屬性值的一份精确拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是記憶體位址(引用類型),拷貝的就是記憶體位址 ,是以如果其中一個對象改變了這個位址,就會影響到另一個對象
如圖:
SourceObject有一個int類型的屬性 "field1"和一個引用類型屬性"refObj"(引用ContainedObject類型的對象)。當對SourceObject做淺拷貝時,建立了CopiedObject,它有一個包含"field1"拷貝值的屬性"field2"以及仍指向refObj本身的引用。由于"field1"是基本類型,是以隻是将它的值拷貝給"field2",但是由于"refObj"是一個引用類型, 是以CopiedObject指向"refObj"相同的位址。是以對SourceObject中的"refObj"所做的任何改變都會影響到CopiedObject
2.深拷貝
深拷貝會拷貝所有的屬性,并拷貝屬性指向的動态配置設定的記憶體。當對象和它所引用的對象一起拷貝時即發生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大,如下圖:
圖中,SourceObject有一個int類型的屬性 "field1"和一個引用類型屬性"refObj1"(引用ContainedObject類型的對象)。當對SourceObject做深拷貝時,建立了CopiedObject,它有一個包含"field1"拷貝值的屬性"field2"以及包含"refObj1"拷貝值的引用類型屬性"refObj2" 。是以對SourceObject中的"refObj"所做的任何改變都不會影響到CopiedObject
如何實作淺拷貝呢?看代碼:
public class User implements Cloneable{
public int age;
public String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
@Override
public String toString() {
return "User [age=" + age + ", name=" + name + ", hashCode()=" + hashCode() + "]";
}
}
public class CloneTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
User u1 = new User(15, "Tom");
User u2 = u1;
User u3 = (User)u1.clone();
System.out.println("u1==u2=" + (u1 == u2));
System.out.println("u1==u3=" + (u1 == u3));
System.out.println("u1.equals(u2)=" + (u1.equals(u2)));
System.out.println("u1.equals(u3)=" + (u1.equals(u3)));
System.out.println("u1:" + u1.toString() + "\nu3=" + u3.toString());
u2.age = 20;
u3.age = 30;
System.out.println("u1:" + u1.toString() + "\nu3=" + u3.toString());
}
}
看運作效果:
u1==u2=true//說明指向同一個引用
u1==u3=false//說明是兩個不同引用,clone了一份
u1.equals(u2)=true//說明指向同一個引用
u1.equals(u3)=false//說明是兩個不同引用,clone了一份
u1:User [age=15, name=Tom, hashCode()=366712642]
u3=User [age=15, name=Tom, hashCode()=1829164700]//基本清單與u1值相同,u1、u3兩個對象
had mod u1:User [age=20, name=Tom, hashCode()=366712642]//u2跟u1是同一個引用,是以修改後對u1有影響
u3=User [age=30, name=Tom, hashCode()=1829164700]//u2跟u1不是同一個引用,是以修改後對u1沒有影響
接着看下面的代碼:
public class ShallwCopy implements Cloneable {
User user;
long a;
ShallwCopy(User u, long b) {
user = u;
a = b;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "ShallwCopy [user=" + user + ", a=" + a + ", hashCode()=" + hashCode() + "]";
}
}
public class CloneTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
User u1 = new User(15, "Tom");
User u2 = u1;
User u3 = (User)u1.clone();
System.out.println("==============shallow copy=====================");
ShallwCopy a1 = new ShallwCopy(u1, 10000);
ShallwCopy a2 = a1;
ShallwCopy a3 = (ShallwCopy) a1.clone();
System.out.println("a1==a2=" + (a1 == a2));
System.out.println("a1==a3=" + (a1 == a3));
System.out.println("a1.equals(a2)=" + (a1.equals(a2)));
System.out.println("a1.equals(a3)=" + (a1.equals(a3)));
System.out.println("a1.user==a3.user=" + (a1.user == a3.user));
System.out.println("a1:" + a1.toString() + "\na3=" + a3.toString());
a3.user.age = 40;
System.out.println("a1:" + a1.toString() + "\na3=" + a3.toString());
}
}
運作結果:
==============shallow copy=====================
a1==a2=true
a1==a3=false
a1.equals(a2)=true
a1.equals(a3)=false
a1.user==a3.user=true
a1:ShallwCopy [user=User [age=20, name=Tom, hashCode()=366712642], a=10000, hashCode()=2018699554]
a3=ShallwCopy [user=User [age=20, name=Tom, hashCode()=366712642], a=10000, hashCode()=1311053135]
had mod a1:ShallwCopy [user=User [age=40, name=Tom, hashCode()=366712642], a=10000, hashCode()=2018699554]
a3=ShallwCopy [user=User [age=40, name=Tom, hashCode()=366712642], a=10000, hashCode()=1311053135]
可以看出a1與a3是兩個對象,但是引用的user對象确是同一個,這就如上圖所說的指向了同一個對象refObj,這時候a3修改user的值時,a1也相應的發生了改變。
那怎麼處理才能保證修改a3,而不修改a1的user引用呢,這就是下面說的深拷貝了,看代碼:
public class DeepCopy implements Cloneable {
User user;
long a;
DeepCopy(User u, long b) {
user = u;
a = b;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// DeepCopy d = (DeepCopy) super.clone();
// d.user = (User) d.user.clone();
DeepCopy d = new DeepCopy((User)user.clone(), a);
return d;
}
@Override
public String toString() {
return "DeepCopy [user=" + user + ", a=" + a + ", hashCode()=" + hashCode() + "]";
}
}
仔細看下,其實這個類跟上面的ShallowCopy不同的地方在 clone()方法
shawllow的clone方法:return super.clone();
而這個類的clone方法:return new DeepCopy((User)user.clone(), a);
測試類調用:
public class CloneTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
User u1 = new User(15, "Tom");
User u2 = u1;
User u3 = (User)u1.clone();
System.out.println("=============deep copy=====================");
DeepCopy d1 = new DeepCopy(u1, 10000);
DeepCopy d2 = d1;
DeepCopy d3 = (DeepCopy) d1.clone();
System.out.println("d1==d2=" + (d1 == d2));
System.out.println("d1==d3=" + (d1 == d3));
System.out.println("d1.equals(d2)=" + (d1.equals(d2)));
System.out.println("d1.equals(d3)=" + (d1.equals(d3)));
System.out.println("d1.user==d3.user=" + (d1.user == d3.user));
System.out.println("d1:" + d1.toString() + "\nd3=" + d3.toString());
d3.user.age = 50;
System.out.println("had mod d1:" + d1.toString() + "\nd3=" + d3.toString());
}
}
看下運作結果:
=============deep copy=====================
d1==d2=true
d1==d3=false
d1.equals(d2)=true
d1.equals(d3)=false
d1.user==d3.user=false
d1:DeepCopy [user=User [age=40, name=Tom, hashCode()=366712642], a=10000, hashCode()=118352462]
d3=DeepCopy [user=User [age=40, name=Tom, hashCode()=1550089733], a=10000, hashCode()=865113938]
had mod d1:DeepCopy [user=User [age=40, name=Tom, hashCode()=366712642], a=10000, hashCode()=118352462]
d3=DeepCopy [user=User [age=50, name=Tom, hashCode()=1550089733], a=10000, hashCode()=865113938]
可以看到d1與d3的user不是同一個引用對象了,是以修改d3的user時,也不會引用到d1的user
好了,以上是個人對cloneable接口與clone方法的代碼實測,如有講的不正确的地方,歡迎指出。