java 防止拷贝_[改善Java代码]避免对象的浅拷贝
建議43: 避免對象的淺拷貝
我們知道一個類實現(xiàn)了Cloneable接口就表示它具備了被拷貝的能力,如果再覆寫clone()方法就會完全具備拷貝能力。拷貝是在內(nèi)存中進行的,所以在性能方面比直接通過new生成對象要快很多,特別是在大對象的生成上,這會使性能的提升非常顯著。但是對象拷貝也有一個比較容易忽略的問題:淺拷貝(Shadow Clone,也叫做影子拷貝)存在對象屬性拷貝不徹底的問題。我們來看這樣一段代碼:
1 public classClient {2 public static voidmain(String[] args) {3 //定義父親
4 Person f = new Person("父親");5 //定義大兒子
6 Person s1 = new Person("大兒子",f);7 //小兒子的信息是通過大兒子拷貝過來的
8 Person s2 =s1.clone();9 s2.setName("小兒子");10 System.out.println(s1.getName() +" 的父親是 " +s1.getFather().getName());11 System.out.println(s2.getName() +" 的父親是 " +s2.getFather().getName());12 }13 }14
15 class Person implementsCloneable{16 //姓名
17 privateString name;18 //父親
19 privatePerson father;20
21 publicPerson(String _name){22 name =_name;23 }24 publicPerson(String _name,Person _parent){25 name =_name;26 father =_parent;27 }28 /*name和parent的getter/setter方法省略*/
29
30 //拷貝的實現(xiàn)
31 @Override32 publicPerson clone(){33 Person p = null;34 try{35 p = (Person) super.clone();36 } catch(CloneNotSupportedException e) {37 e.printStackTrace();38 }39 returnp;40 }41 publicString getName() {42 returnname;43 }44 public voidsetName(String name) {45 this.name =name;46 }47 publicPerson getFather() {48 returnfather;49 }50 public voidsetFather(Person father) {51 this.father =father;52 }53 }
程序中,我們描述了這樣一個場景:一個父親,有兩個兒子,大小兒子同根同種,所以小兒子對象就通過拷貝大兒子對象來生成,運行輸出的結果如下:
大兒子 的父親是 父親
小兒子 的父親是 父親
這很正確,沒有問題。突然有一天,父親心血來潮想讓大兒子去認個干爹,也就是大兒子的父親名稱需要重新設置一下,代碼如下:
1 public static voidmain(String[] args) {2 //定義父親
3 Person f = new Person("父親");4 //定義大兒子
5 Person s1 = new Person("大兒子",f);6 //小兒子的信息是通過大兒子拷貝過來的
7 Person s2 =s1.clone();8 s2.setName("小兒子");9 //認干爹
10 s1.getFather().setName("干爹");11 System.out.println(s1.getName() +" 的父親是 " +s1.getFather().getName());12 System.out.println(s2.getName() +" 的父親是 " +s2.getFather().getName());13 }
上面僅僅修改了加粗字體部分,大兒子重新設置了父親名稱,我們期望的輸出是:將大兒子父親的名稱修改為干爹,小兒子的父親名稱保持不變。下面來檢查一下結果是否如此:
大兒子 的父親是 干爹
小兒子 的父親是 干爹
怎么回事,小兒子的父親也成了“干爹”?兩個兒子都沒有,豈不是要氣死“父親”了!出現(xiàn)這個問題的原因就在于clone方法,我們知道所有類都繼承自Object,Object提供了一個對象拷貝的默認方法,即上面代碼中的super.clone方法,但是該方法是有缺陷的,它提供的是一種淺拷貝方式,也就是說它并不會把對象的所有屬性全部拷貝一份,而是有選擇性的拷貝,它的拷貝規(guī)則如下:
(1)基本類型
如果變量是基本類型,則拷貝其值,比如int、float等。
(2)對象
如果變量是一個實例對象,則拷貝地址引用,也就是說此時新拷貝出的對象與原有對象共享該實例變量,不受訪問權限的限制。這在Java中是很瘋狂的,因為它突破了訪問權限的定義:一個private修飾的變量,竟然可以被兩個不同的實例對象訪問,這讓Java的訪問權限體系情何以堪!
(3)String字符串
這個比較特殊,拷貝的也是一個地址,是個引用,但是在修改時,它會從字符串池(String Pool)中重新生成新的字符串,原有的字符串對象保持不變,在此處我們可以認為String是一個基本類型。(有關字符串的知識詳見第4章。)
明白了這三個規(guī)則,上面的例子就很清晰了,小兒子對象是通過拷貝大兒子產(chǎn)生的,其父親都是同一個人,也就是同一個對象,大兒子修改了父親名稱,小兒子也就跟著修改了—于是,父親的兩個兒子都沒了!其實要更正也很簡單,clone方法的代碼如下:
publicPerson clone(){
Person p= null;try{
p= (Person) super.clone();
p.setFather(newPerson(p.getFather().getName()));
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}returnp;
}
然后再運行,小兒子的父親就不會是“干爹”了。如此就實現(xiàn)了對象的深拷貝(Deep Clone),保證拷貝出來的對象自成一體,不受“母體”的影響,和new生成的對象沒有任何區(qū)別。
注意 淺拷貝只是Java提供的一種簡單拷貝機制,不便于直接使用。
總結
以上是生活随笔為你收集整理的java 防止拷贝_[改善Java代码]避免对象的浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker java 不兼容_Appl
- 下一篇: java下载文件跳转页面_java se