簡書的編輯器不好用,部分內容只能截圖發(fā)表了罢艾。
新建一個人員類楣颠,并聲明姓名尽纽、性別、年齡等屬性童漩。
這樣就實現(xiàn)了對象的復制弄贿,對象p2與對象p1具有完全相同的屬性——你也許認為:對象的復制既然如此簡單,為什么還需要使用clone方法呢矫膨?
我們發(fā)現(xiàn):當改變了p2的name值之后差凹,p1的值也跟著發(fā)生了改變。現(xiàn)在p1的name已經不是Caesar侧馅,和p2對象一樣危尿,它們的name都是Alexander。這是為什么呢馁痴?實際上這種賦值操作并沒有真正在內存中創(chuàng)建一個新的的對象谊娇,Person p2 = p1;實際上是把p1的引用賦給了p2,它們指向的是堆內存中同一塊內存地址罗晕。因此p1和p2的操縱的都是同一個對象济欢。
用==操作符判斷p1與p2是否同一個對象,結果為true小渊。
下面我們使用簡單的原型模式來改造Person類法褥,使其能夠完成真正意義上的對象克隆
測試后,我們發(fā)現(xiàn)通過p1的clone方法復制了一個一模一樣的p2對象〕晏耄現(xiàn)在我們用==操作符半等,看下這兩個對象的內存地址是否相同:
輸出結果為false。現(xiàn)在我們已經完全確定p1和p2并非同一個內存對象了呐萨。
這里涉及了兩個概念:淺克隆與深克隆杀饵。以上的clone方式是淺克隆,接下來我們演示一下深克隆的demo垛吗。
在人員類中聲明一個Address實例凹髓。
創(chuàng)建一個Address對象,并把地址名稱設為“北京”怯屉,將該address對象賦值給Person類蔚舀。
利用原型對象的克隆方法得到p2對象;接下來我們改變了address的地址為“南京”锨络,然后我們發(fā)現(xiàn)p1和p2的地址都發(fā)生了改變赌躺。
用==操作符比較發(fā)現(xiàn),p1和p2的address是同一個對象羡儿。這就是淺克隆礼患,如果原型對象的成員變量為引用類型,那么僅會把成員變量的引用復制一份,給克隆對象缅叠,并沒有真正的開辟一塊內存空間悄泥。
正如我們所看到的,淺克隆不會真正的復制引用類型的對象肤粱,而只是復制一份對象的引用弹囚。但是這里我們我們會發(fā)現(xiàn)一個問題:String同樣也是引用類型,為什么在淺克隆的時候领曼,我們改變p2的name屬性時鸥鹉,p1的name屬性沒有發(fā)生改變呢?很明顯庶骄,在淺克隆時毁渗,String對象并非僅僅復制了引用,而是真實的在堆內存中創(chuàng)建了兩個不同的對象——之前遺漏了對這一結果的測試单刁,現(xiàn)在補上:
通過上面的結果我們可以看到:p2的name發(fā)生改變后灸异,p1的name并沒有發(fā)生改變。也就是說幻碱,通過clone我們得到了兩個不同的string對象绎狭!
其實這里本來不想對這一結果進行過多的解釋细溅,但是繞過去不談總有些說不過去褥傍,因此這里簡短的談一下String類的特殊性。以后會在寫一片博客喇聊,對String類有一個詳細的分析恍风。
這里其實涉及到String常量池的概念,當使用String str = "abc";賦值時誓篱,實際上是在常量池創(chuàng)建了一個對象(也可能不會創(chuàng)建對象朋贬,如果常量池中已經存在的話)。而常量池中的對象其實是不可更改的窜骄,因此锦募,在調用p2的setName方法時,實際上是創(chuàng)建了一個新的字符串對象邻遏。介紹完這些糠亩,我們再去看一下深克隆模式:
在上面的例子中,我們使Address類也實現(xiàn)了Cloneable接口准验,并且重寫了clone方法赎线。
修改Person類的clone方法,然后調用address的clone方法糊饱,將得到的address對象賦給當前address成員變量垂寥。
經過深克隆之后,我們再用==操作符比較p1的address和p2的address,發(fā)現(xiàn)結果為false滞项,證明Person類的引用類型的對象真正被克隆了狭归。
由于clone方法時一個native方法,因此它在提供便利的同時文判,原型模式復制對象的性能也是最快的唉铜。