淺拷貝和深拷貝是什么稚虎?
淺拷貝和深拷貝都是針對已經(jīng)存在了的對象的操作砂代,在java中,基本數(shù)據(jù)類型有八種,和引用數(shù)據(jù)類型桩撮。在程序中,一般用 =
來做賦值的操作崔兴,對于基本數(shù)據(jù)類型印机,實際上是拷貝它的值,而對于引用數(shù)據(jù)類型鞭衩,則是拷貝的它的引用地址学搜,舉例如下:
@Test
public void m2() {
int i = 1;
int j = i;
System.out.println(j);
Student stu1 = new Student("zhangsan", 12);
System.out.println(stu1);
Student stu2 = stu1;
stu2.setAge(20);
System.out.println(stu1);
System.out.println(stu2);
}
運(yùn)行結(jié)果:
1
Student{name='zhangsan', age=12, active=null}
Student{name='zhangsan', age=20, active=null}
Student{name='zhangsan', age=20, active=null}
從運(yùn)行的結(jié)果,發(fā)現(xiàn)一個問題论衍,int類型的值不用多說瑞佩,而在引用類型中,明明修改的是stu2的age坯台,為什么stu1的age也發(fā)生了變化炬丸,這里就涉及到了值傳遞和引用傳遞的問題了,ok蜒蕾,后面介紹稠炬。
Java中的clone()方法
說到拷貝操作,那java必定提供了API來供我們使用滥搭,那就是Object類中的clone()方法了(它是一個native方法)酸纲,既然是超類中的protected方法,那么子類中就有必要重寫一下瑟匆,所有使用調(diào)用clone()來拷貝的時候闽坡,其對象必須要實現(xiàn)標(biāo)識接口Cloneable,否則就會拋出CloneNotSupportedException這個異常愁溜。
淺拷貝
首先創(chuàng)建一個Studen類疾嗅,并實現(xiàn)Cloneable接口,重寫clone方法冕象。
public class Student implements Cloneable {
private String name;
private int age;
private Active active;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 省略get代承、set方法
}
Active類的實現(xiàn):
public class Active implements Cloneable {
private String name;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Active(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
測試用例:用clone方法,復(fù)制新new出來的student1渐扮,得到student2,再做各種比較论悴。
@Test
public void m1() throws Exception {
Student student1 = new Student();
student1.setAge(10);
student1.setName("zhangsan");
student1.setActive(new Active("pingpang"));
// 原始對象的hashcode
System.out.println(student1.hashCode());
// 克隆出來的對象
Student student2 = (Student) student1.clone();
// 克隆出來的對象的hashcode
System.out.println(student2.hashCode());
// 兩個對象是否一樣
System.out.println(student1 == student2);
// 兩個對象比較
System.out.println(student1);
System.out.println(student2);
// 兩個對象各自的引用比較
System.out.println(student1.getActive());
System.out.println(student2.getActive());
}
運(yùn)行結(jié)果:
697960108
943010986
false
Student@299a06ac
Student@383534aa
Active@6bc168e5
Active@6bc168e5
可以得出關(guān)于淺拷貝的幾點結(jié)論:
- clone()方法掖棉,會創(chuàng)建一個新的對象。
- 淺拷貝基本數(shù)據(jù)類型是拷貝值膀估。
- 拷貝引用數(shù)據(jù)類型如:
Active
幔亥,則拷貝的是它的引用地址。
深拷貝
Student類察纯,在克隆此類對象的時候帕棉,將此對象中的引用屬性也克隆一份,只有clone方法不同:
public class Student implements Cloneable {
private String name;
private int age;
private Active active;
public Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.active = (Active) this.active.clone();
return student;
}
// get饼记、set方法省略
}
Active類香伴,實現(xiàn)Cloneable接口,并重寫了clone方法:
public class Active implements Cloneable {
private String name;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Active(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
測試用例:
@Test
public void m1() throws Exception {
Student student1 = new Student();
student1.setAge(10);
student1.setName("zhangsan");
student1.setActive(new Active("pingpang"));
// 原始對象的hashcode
System.out.println(student1.hashCode());
// 克隆出來的對象
Student student2 = (Student) student1.clone();
// 克隆出來的對象的hashcode
System.out.println(student2.hashCode());
// 兩個對象是否一樣
System.out.println(student1 == student2);
// 兩個對象比較
System.out.println(student1);
System.out.println(student2);
// 兩個對象各自的引用比較
System.out.println(student1.getActive());
System.out.println(student2.getActive());
}
測試結(jié)果中顯示不同student對象中的active對象也不同了:
697960108
943010986
false
com.nmys.story.interview.copy_interview.Student@299a06ac
com.nmys.story.interview.copy_interview.Student@383534aa
com.nmys.story.interview.copy_interview.Active@6bc168e5
com.nmys.story.interview.copy_interview.Active@7b3300e5
可以得出深拷貝的幾點結(jié)論:
- 深拷貝是完全創(chuàng)建一個新的對象
- 深拷貝也會將原始對象中的引用對象重新復(fù)制一份
總結(jié)
- 一定要實現(xiàn)Cloneable接口具则。
- 重寫clone()方法即纲,注意:默認(rèn)是淺拷貝,這里需要將引用類型進(jìn)行深拷貝處乡洼。
- 特殊:String類雖然是引用類型崇裁,但是是final類匕坯,同時也有字符串常量池的存在束昵,不必進(jìn)行處理。
- 深拷貝可以通過上面所說的clone方法實現(xiàn)葛峻,還可以通過序列化來實現(xiàn)锹雏。
- 當(dāng)時寫測試用例的時候,我這里用了lombok的注解@Data术奖,為的就是不用手寫get礁遵、set方法了,但測試的時候采记,發(fā)現(xiàn)clone出來的對象的hashcode是一樣的佣耐,原因是lombok在為我們生成get、set的同時唧龄,toString兼砖、hashCode、equals等方法都替我們重寫了既棺,導(dǎo)致clone出來的類雖然
==
是false
讽挟,但是hashCode卻一樣,建議還是手動寫比較好丸冕。 - 最后再總結(jié)下耽梅,假設(shè),A 類中引用了 B 類胖烛,B 類中引用了 C 類眼姐,對象分別對應(yīng) a诅迷,b,c众旗,淺拷貝 a 的結(jié)果 a1竟贯,b,c逝钥。深拷貝 a 的結(jié)果 a1屑那,b1,c1艘款。