深拷貝與淺拷貝初探
先說(shuō)說(shuō)淺拷貝...
一個(gè)類(lèi)的拷貝構(gòu)造方法通常實(shí)現(xiàn)為成員變量逐域賦值转培,即將當(dāng)前對(duì)象的各個(gè)成員變量賦值為實(shí)際參數(shù)對(duì)應(yīng)的各個(gè)成員變量的 值 恶导,稱(chēng)為淺拷貝。
淺拷貝有什么弱點(diǎn)呢浸须?
① 當(dāng)成員變量的數(shù)據(jù)類(lèi)型是基本數(shù)據(jù)類(lèi)型時(shí)惨寿,(int float double char boolean)
淺拷貝能夠?qū)崿F(xiàn)對(duì)象的復(fù)制功能,這個(gè)復(fù)制是完整的删窒,沒(méi)有隱患的裂垦。
② 當(dāng)成員變量是引用數(shù)據(jù)類(lèi)型時(shí),淺拷貝僅僅只是復(fù)制了 這一層引用關(guān)系肌索。
而并沒(méi)有實(shí)現(xiàn)對(duì)象的復(fù)制功能蕉拢,這個(gè)復(fù)制是有隱患的。
TheClass(SomeClass ptr_obj) //拷貝構(gòu)造方法
{
this.MemberVar = ptr_obj.MemberVar;
//逐個(gè)域賦值成員變量 初始化當(dāng)前實(shí)例
}
這個(gè)隱患在于:當(dāng)兩個(gè)引用同時(shí)指向一個(gè)對(duì)象時(shí)诚亚,例如當(dāng) SeqList1 的成員數(shù)組 element1,
經(jīng)過(guò)淺拷貝后的對(duì)象 SeqList2 的成員數(shù)組變量 element2晕换,這兩個(gè)引用變量引用的是同一個(gè)數(shù)組。
當(dāng)在進(jìn)行 element1 的數(shù)組元素的修改時(shí)(修改包括:插入站宗、刪除...)届巩,
實(shí)際上也影響了 element2 的元素,但是并沒(méi)有任何代碼去更改 element2 的長(zhǎng)度份乒。
public SeqList(SeqList<T>list){
thsi.n = list.n;
this.element = list.element; //兩者引用同一個(gè)數(shù)組
}
下面我們舉個(gè)例子:
String[] values = {"a","b","c","d"};
SeqList<String> list1 = new SeqList<String>(values); //正常地通過(guò)傳入數(shù)組初始化
SeqList<String> list2 = new SeqList<String>(list1); //通過(guò)拷貝方法來(lái)初始化
list1.remove(0);
System.out.println("list1 :" + list1.toString());
System.out.println("list2 :" + list2.toString());
因?yàn)閯h除了數(shù)組的第一項(xiàng)恕汇,但 list2 自己并不知曉這一點(diǎn)腕唧,依然在 toString() 時(shí)會(huì)輸出 element[0] 這一項(xiàng),所以運(yùn)行時(shí)在輸出 list2 時(shí)會(huì)報(bào)錯(cuò):
list1 : {b,c,d}
Exception in thread "main" java.lang.NullPointerException
所以為了消除這種隱患瘾英,我們需要深拷貝枣接。
再來(lái)說(shuō)說(shuō)深拷貝...
看了上面淺拷貝的同學(xué)們一定心里有數(shù),淺拷貝的弱點(diǎn)就是沒(méi)有實(shí)際拷貝 引用指向的那個(gè)對(duì)象或是變量缺谴,究其原因就是因?yàn)闆](méi)有 新開(kāi)辟內(nèi)存空間來(lái)存放一個(gè)復(fù)制得到的新對(duì)象但惶。
所以深拷貝不僅要做到淺拷貝里做到的拷貝對(duì)象的各個(gè)成員變量的值,還要為引用變量新開(kāi)辟內(nèi)存空間湿蛔,并復(fù)制其中的所有元素或是對(duì)象膀曾。
但是 深拷貝中有一個(gè)很常見(jiàn)的雷區(qū)!
如下我貼出了一個(gè)并不正確的深拷貝構(gòu)造方法:
public SeqList(SeqList<? extends T>list){
this.n = list.n; //基礎(chǔ)類(lèi)型變量用 賦值 來(lái) 復(fù)制
this.element = new Object[list.element.length]; //開(kāi)辟空間
// 但這個(gè)空間并沒(méi)有任何實(shí)際意義阳啥,因?yàn)槌刈永锸裁匆矝](méi)有
for(int i=0; i<list.n; i++){
this.element[i] = list.element[i];
// 其實(shí)拷貝的還是引用關(guān)系添谊,別忘了 C/C++ 和 java 中
// xxx[yyy] 表示的仍是一個(gè)指針引用關(guān)系,其實(shí)還是一個(gè)地址指針/引用
// 引用的是 list.element 對(duì)應(yīng)的數(shù)組元素察迟。
}
}
SeqList<StringBuffer>lista = new SeqList<StringBuffer>(n-1);
for(int i=0; i<n; ++i){
lista.insert(new StringBuffer((char)('A'+i)+""));
}
// 通過(guò) 假的"深拷貝" 構(gòu)造:
SeqList<StringBuffer>listb = new SeqList<StringBuffer>(lista);
lista.insert(new StringBuffer("F"));
listb.remove(listb.size()-1);
// 插入和刪除 改變的只是添一個(gè)指針和砍掉一個(gè)指針而已
// 所以 lista 的改變對(duì) listb 沒(méi)有影響
StringBuffer strbuf = lista.get(0);
strbuf.setCharAt(0,'X');
lista.set(0,strbuf);
// 涉及到了對(duì)象引用的元素的實(shí)例值斩狱。
此時(shí),lista 和 listb 引用的是同一個(gè)池子中(同一塊內(nèi)存地址)扎瓶。
這一組元素實(shí)例值 = {a,b,c,d}
假如此時(shí)所踊,將 lista[0] 修改為 "x",仍然會(huì)影響到 listb.
最終...
這里有一份比較直觀的圖例:
從此 list1 和 list2 就是完全獨(dú)立概荷,不相干的兩個(gè)變量了秕岛。
當(dāng)對(duì)數(shù)組元素做修改時(shí),他們修改的都是自己儲(chǔ)存空間里放的那一列元素误证。
可具體要怎么做才能實(shí)現(xiàn)深拷貝呢瓣蛀?
我們有所有類(lèi)的爸爸 Object 呀!
它有11個(gè)方法雷厂,有兩個(gè)protected的方法惋增,其中一個(gè)為clone方法。
該方法聲明如下:
protected native Object clone() throws CloneNotSupportedException;
因?yàn)槊總€(gè)類(lèi)直接或間接的父類(lèi)都是Object改鲫,因此它們都含有clone()方法诈皿,但是因?yàn)樵摲椒ㄊ莗rotected,所以都不能在類(lèi)外進(jìn)行訪(fǎng)問(wèn)像棘。
要想對(duì)一個(gè)對(duì)象進(jìn)行復(fù)制稽亏,就需要對(duì)clone方法覆蓋。
@Override
public Object clone() {
SeqList toList = null;
try{
toList = (SeqList)super.clone();
// 這里用到了 對(duì)父類(lèi)的向下轉(zhuǎn)型
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return toList;
}
總結(jié)
所以深拷貝應(yīng)該是缕题,復(fù)制每個(gè)引用類(lèi)型成員變量所引用的數(shù)組或是對(duì)象截歉,
直到該對(duì)象能達(dá)到的所有對(duì)象及實(shí)例。
深拷貝/淺拷貝看上去是一個(gè)非常小非常細(xì)的知識(shí)點(diǎn)烟零,但是其實(shí)能夠體現(xiàn)很多
類(lèi)瘪松、對(duì)象和方法的基礎(chǔ)思想咸作。
這是我第一次系統(tǒng)性的總結(jié)一個(gè)知識(shí)點(diǎn),希望以后能做得更好宵睦。
具體針對(duì)clone方法记罚,我們還有一大堆可以說(shuō)的。
敬請(qǐng)期待下回分解壳嚎。