第12章_傳遞和返回對象解析_第1頁
第12章_傳遞和返回對象解析_第2頁
第12章_傳遞和返回對象解析_第3頁
第12章_傳遞和返回對象解析_第4頁
第12章_傳遞和返回對象解析_第5頁
已閱讀5頁,還剩13頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、第 12 章 傳遞和返回對象到目前為止,讀者應(yīng)對對象的“傳遞”有了一個(gè)較為深刻的認(rèn)識(shí),記住實(shí)際 傳遞的只是一個(gè)句柄。在許多程序設(shè)計(jì)語言中,我們可用語言的“普通”方式到處傳遞對象,而且 大多數(shù)時(shí)候都不會(huì)遇到問題。 但有些時(shí)候卻不得不采取一些非常做法, 使得情況 突然變得稍微復(fù)雜起來(在C+中則是變得非常復(fù)雜)。Java亦不例外,我們十 分有必要準(zhǔn)確認(rèn)識(shí)在對象傳遞和賦值時(shí)所發(fā)生的一切。這正是本章的宗旨。若讀者是從某些特殊的程序設(shè)計(jì)環(huán)境中轉(zhuǎn)移過來的,那么一般都會(huì)問到:“Java有指針嗎?”有些人認(rèn)為指針的操作很困難,而且十分危險(xiǎn),所以一廂情 愿地認(rèn)為它沒有好處。同時(shí)由于 Java 有如此好的口碑,所

2、以應(yīng)該很輕易地免除 自己以前編程中的麻煩,其中不可能夾帶有指針這樣的“危險(xiǎn)品” 。然而準(zhǔn)確地 說,Java是有指針的!事實(shí)上,Java中每個(gè)對象(除基本數(shù)據(jù)類型以外)的標(biāo)識(shí) 符都屬于指針的一種。 但它們的使用受到了嚴(yán)格的限制和防范, 不僅編譯器對它 們有“戒心”,運(yùn)行期系統(tǒng)也不例外?;蛘邠Q從另一個(gè)角度說,Java有指針,但沒有傳統(tǒng)指針的麻煩。我曾一度將這種指針叫做“句柄” ,但你可以把它想像成“安全指針”。和預(yù)備學(xué)校為學(xué)生提供的安全剪刀類似除非特別有意,否則 不會(huì)傷著自己,只不過有時(shí)要慢慢來,要習(xí)慣一些沉悶的工作。12.1 傳遞句柄 將句柄傳遞進(jìn)入一個(gè)方法時(shí),指向的仍然是相同的對象。一個(gè)簡單的

3、實(shí)驗(yàn)可 以證明這一點(diǎn)(若執(zhí)行這個(gè)程序時(shí)有麻煩,請參考第 3章 3.1.2小節(jié)“賦值”):542 頁程序toString方法會(huì)在打印語句里自動(dòng)調(diào)用,而 PassHandles直接從Object繼承, 沒有toString的重新定義。因此,這里會(huì)采用toString的Object版本,打印出對 象的類,接著是那個(gè)對象所在的位置(不是句柄,而是對象的實(shí)際存儲(chǔ)位置) 。 輸出結(jié)果如下:p inside main(): PassHandles1653748h inside f() : PassHandles1653748可以看到,無論 p 還是 h 引用的都是同一個(gè)對象。這比復(fù)制一個(gè)新的 PassHa

4、ndles對象有效多了,使我們能將一個(gè)參數(shù)發(fā)給一個(gè)方法。但這樣做也帶 來了另一個(gè)重要的問題。12.1.1 別名問題 “別名”意味著多個(gè)句柄都試圖指向同一個(gè)對象,就象前面的例子展示的那 樣。若有人向那個(gè)對象里寫入一點(diǎn)什么東西, 就會(huì)產(chǎn)生別名問題。 若其他句柄的 所有者不希望那個(gè)對象改變, 恐怕就要失望了。 這可用下面這個(gè)簡單的例子說明:543 頁程序?qū)ο旅孢@行:Alias1 y = x; / Assign the handle它會(huì)新建一個(gè) Alias1 句柄,但不是把它分配給由 new 創(chuàng)建的一個(gè)新鮮對象, 而是分配給一個(gè)現(xiàn)有的句柄。 所以句柄 x 的內(nèi)容即對象 x 指向的地址被 分配給y,所以

5、無論x還是y都與相同的對象連接起來。這樣一來,一旦x的i在下述語句中增值:x.i+;y的i值也必然受到影響。從最終的輸出就可以看出:544 頁上程序此時(shí)最直接的一個(gè)解決辦法就是干脆不這樣做: 不要有意將多個(gè)句柄指向同 一個(gè)作用域內(nèi)的同一個(gè)對象。 這樣做可使代碼更易理解和調(diào)試。 然而,一旦準(zhǔn)備 將句柄作為一個(gè)自變量或參數(shù)傳遞這是 Java 設(shè)想的正常方法別名問題 就會(huì)自動(dòng)出現(xiàn),因?yàn)閯?chuàng)建的本地句柄可能修改“外部對象” (在方法作用域之外 創(chuàng)建的對象)。下面是一個(gè)例子:544 頁程序輸出如下:x: 7Calling f(x)x: 8方法改變了自己的參數(shù)外部對象。一旦遇到這種情況,必須判斷它是否 合理

6、,用戶是否愿意這樣,以及是不是會(huì)造成問題。通常,我們調(diào)用一個(gè)方法是為了產(chǎn)生返回值,或者用它改變?yōu)槠湔{(diào)用方法的 那個(gè)對象的狀態(tài)(方法其實(shí)就是我們向那個(gè)對象“發(fā)一條消息”的方式) 。很少 需要調(diào)用一個(gè)方法來處理它的參數(shù); 這叫作利用方法的“副作用”(Side Effect)。 所以倘若創(chuàng)建一個(gè)會(huì)修改自己參數(shù)的方法, 必須向用戶明確地指出這一情況, 并 警告使用那個(gè)方法可能會(huì)有的后果以及它的潛在威脅。由于存在這些混淆和缺 陷,所以應(yīng)該盡量避免改變參數(shù)。若需在一個(gè)方法調(diào)用期間修改一個(gè)參數(shù),且不打算修改外部參數(shù),就應(yīng)在自 己的方法內(nèi)部制作一個(gè)副本, 從而保護(hù)那個(gè)參數(shù)。 本章的大多數(shù)內(nèi)容都是圍繞這 個(gè)問題

7、展開的。12.2 制作本地副本稍微總結(jié)一下:Java中的所有自變量或參數(shù)傳遞都是通過傳遞句柄進(jìn)行的。 也就是說,當(dāng)我們傳遞“一個(gè)對象”時(shí),實(shí)際傳遞的只是指向位于方法外部的那 個(gè)對象的“一個(gè)句柄” 。所以一旦要對那個(gè)句柄進(jìn)行任何修改,便相當(dāng)于修改外 部對象。此外:參數(shù)傳遞過程中會(huì)自動(dòng)產(chǎn)生別名問題不存在本地對象,只有本地句柄句柄有自己的作用域,而對象沒有對象的“存在時(shí)間”在Java里不是個(gè)冋題沒有語言上的支持(如常量)可防止對象被修改(以避免別名的副作用)若只是從對象中讀取信息,而不修改它,傳遞句柄便是自變量傳遞中最有效 的一種形式。這種做非常恰當(dāng);默認(rèn)的方法一般也是最有效的方法。然而,有時(shí) 仍需

8、將對象當(dāng)作“本地的”對待,使我們作出的改變只影響一個(gè)本地副本,不會(huì) 對外面的對象造成影響。 許多程序設(shè)計(jì)語言都支持在方法內(nèi)自動(dòng)生成外部對象的 一個(gè)本地副本(注釋)。盡管Java不具備這種能力,但允許我們達(dá)到同樣的效 果。:在C語言中,通??刂频氖巧倭繑?shù)據(jù)位,默認(rèn)操作是按值傳遞。 C+也 必須遵照這一形式,但按值傳遞對象并非肯定是一種有效的方式。 此外,在C+ 中用于支持按值傳遞的代碼也較難編寫,是件讓人頭痛的事情。12.2.1 按值傳遞首先要解決術(shù)語的問題,最適合“按值傳遞”的看起來是自變量。 “按值傳 遞”以及它的含義取決于如何理解程序的運(yùn)行方式。 最常見的意思是獲得要傳遞 的任何東西的一個(gè)

9、本地副本,但這里真正的問題是如何看待自己準(zhǔn)備傳遞的東 西。對于“按值傳遞”的含義,目前存在兩種存在明顯區(qū)別的見解:(1) Java按值傳遞任何東西。若將基本數(shù)據(jù)類型傳遞進(jìn)入一個(gè)方法,會(huì)明確 得到基本數(shù)據(jù)類型的一個(gè)副本。 但若將一個(gè)句柄傳遞進(jìn)入方法, 得到的是句柄的 副本。所以人們認(rèn)為“一切”都按值傳遞。當(dāng)然,這種說法也有一個(gè)前提:句柄 肯定也會(huì)被傳遞。但 Java 的設(shè)計(jì)方案似乎有些超前,允許我們忽略(大多數(shù)時(shí) 候)自己處理的是一個(gè)句柄。也就是說,它允許我們將句柄假想成“對象” ,因 為在發(fā)出方法調(diào)用時(shí),系統(tǒng)會(huì)自動(dòng)照管兩者間的差異。(2) Java主要按值傳遞(無自變量),但對象卻是按引用傳遞

10、的。得到這個(gè)結(jié) 論的前提是句柄只是對象的一個(gè)“別名” ,所以不考慮傳遞句柄的問題,而是直 接指出“我準(zhǔn)備傳遞對象” 。由于將其傳遞進(jìn)入一個(gè)方法時(shí)沒有獲得對象的一個(gè) 本地副本,所以對象顯然不是按值傳遞的。Sun公司似乎在某種程度上支持這一 見解,因?yàn)樗氨A舻磳?shí)現(xiàn)”的關(guān)鍵字之一便是 byvalue (按值)。但沒人知道 那個(gè)關(guān)鍵字什么時(shí)候可以發(fā)揮作用。盡管存在兩種不同的見解,但其間的分歧歸根到底是由于對“句柄”的不同 解釋造成的。 我打算在本書剩下的部分里回避這個(gè)問題。 大家不久就會(huì)知道, 這 個(gè)問題爭論下去其實(shí)是沒有意義的最重要的是理解一個(gè)句柄的傳遞會(huì)使調(diào) 用者的對象發(fā)生意外的改變。12.2

11、.2 克隆對象 若需修改一個(gè)對象,同時(shí)不想改變調(diào)用者的對象,就要制作該對象的一個(gè)本 地副本。 這也是本地副本最常見的一種用途。 若決定制作一個(gè)本地副本, 只需簡 單地使用clone()方法即可。Clone是“克隆”的意思,即制作完全一模一樣的副 本。這個(gè)方法在基礎(chǔ)類 Object中定義成“ protected ”(受保護(hù))模式。但在希望 克隆的任何衍生類中,必須將其覆蓋為“ public ”模式。例如,標(biāo)準(zhǔn)庫類 Vector 覆蓋了 clone(),所以能為Vector調(diào)用clone(),如下所示:547 頁程序clone()方法產(chǎn)生了一個(gè)Object,后者必須立即重新造型為正確類型。這個(gè)例子

12、指出Vector的clone()方法不能自動(dòng)嘗試克隆 Vector內(nèi)包含的每個(gè)對象由于別名問題,老的 Vector 和克隆的 Vector 都包含了相同的對象。我們通常把這 種情況叫作“簡單復(fù)制”或者“淺層復(fù)制” ,因?yàn)樗粡?fù)制了一個(gè)對象的“表面” 部分。實(shí)際對象除包含這個(gè)“表面”以外,還包括句柄指向的所有對象,以及那 些對象又指向的其他所有對象,由此類推。這便是“對象網(wǎng)”或“對象關(guān)系網(wǎng)” 的由來。若能復(fù)制下所有這張網(wǎng),便叫作“全面復(fù)制”或者 “深層復(fù)制”。在輸出中可看到淺層復(fù)制的結(jié)果,注意對 v2 采取的行動(dòng)也會(huì)影響到 v:548 頁上程序一般來說,由于不敢保證 Vector里包含的對象是“

13、可以克隆”(注釋)的, 所以最好不要試圖克隆那些對象。:“可以克隆”用英語講是cioneable,請留意Java庫中專門保留了這樣的 一個(gè)關(guān)鍵字。12.2.3 使類具有克隆能力盡管克隆方法是在所有類最基本的 Object中定義的,但克隆仍然不會(huì)在每個(gè) 類里自動(dòng)進(jìn)行。這似乎有些不可思議, 因?yàn)榛A(chǔ)類方法在衍生類里是肯定能用的。 但 Java 確實(shí)有點(diǎn)兒反其道而行之;如果想在一個(gè)類里使用克隆方法,唯一的辦 法就是專門添加一些代碼,以便保證克隆的正常進(jìn)行。1.使用protected時(shí)的技巧為避免我們創(chuàng)建的每個(gè)類都默認(rèn)具有克隆能力,clone()方法在基礎(chǔ)類Object 里得到了“保留”(設(shè)為prot

14、ected) 0這樣造成的后果就是:對那些簡單地使用一 下這個(gè)類的客戶程序員來說, 他們不會(huì)默認(rèn)地?fù)碛羞@個(gè)方法; 其次,我們不能利 用指向基礎(chǔ)類的一個(gè)句柄來調(diào)用cione()(盡管那樣做在某些情況下特別有用,比如用多形性的方式克隆一系列對象) 。在編譯期的時(shí)候,這實(shí)際是通知我們對象 不可克隆的一種方式一一而且最奇怪的是,Java庫中的大多數(shù)類都不能克隆。因 此,假如我們執(zhí)行下述代碼:Integer x = new Integer(l);x = x.clone();那么在編譯期, 就有一條討厭的錯(cuò)誤消息彈出, 告訴我們不可訪問 clone()因?yàn)镮nteger并沒有覆蓋它,而且它對 protec

15、ted版本來說是默認(rèn)的)。但是,假若我們是在一個(gè)從 Object 衍生出來的類中(所有類都是從 Object 衍生的),就有權(quán)調(diào)用Object.clone(),因?yàn)樗恰?protected,”而且我們在一個(gè)繼 承器中。基礎(chǔ)類clone()提供了一個(gè)有用的功能一一它進(jìn)行的是對衍生類對象的真 正“按位”復(fù)制,所以相當(dāng)于標(biāo)準(zhǔn)的克隆行動(dòng)0然而,我們隨后需要將自己的克 隆操作設(shè)為public,否則無法訪問。總之,克隆時(shí)要注意的兩個(gè)關(guān)鍵問題是:幾 乎肯定要調(diào)用super.clone(,以及注意將克隆設(shè)為public。有時(shí)還想在更深層的衍生類中覆蓋cione(),否則就直接使用我們的clone()(現(xiàn)在已

16、成為 public) ,而那并不一定是我們所希望的 (然而,由于 Object.clone() 已制作了實(shí)際對象的一個(gè)副本,所以也有可能允許這種情況) 。 protected 的技巧 在這里只能用一次: 首次從一個(gè)不具備克隆能力的類繼承, 而且想使一個(gè)類變成“能夠克隆”。而在從我們的類繼承的任何場合,clone()方法都是可以使用的, 因?yàn)?Java 不可能在衍生之后反而縮小方法的訪問范圍。換言之,一旦對象變得 可以克隆, 從它衍生的任何東西都是能夠克隆的, 除非使用特殊的機(jī)制 (后面討 論)令其“關(guān)閉”克隆能力。2.實(shí)現(xiàn) Cloneable 接口為使一個(gè)對象的克隆能力功成圓滿,還需要做另一件

17、事情:實(shí)現(xiàn) Cloneable 接口。這個(gè)接口使人稍覺奇怪,因?yàn)樗强盏?!interface Cloneable 之所以要實(shí)現(xiàn)這個(gè)空接口, 顯然不是因?yàn)槲覀儨?zhǔn)備上溯造型成一個(gè) Cloneable,以及調(diào)用它的某個(gè)方法。有些人認(rèn)為在這里使用接口屬于一種“欺騙”行為,因?yàn)樗褂玫奶匦源虻氖莿e的主意,而非原來的意思。 Cloneable interface 的實(shí)現(xiàn)扮演了一個(gè)標(biāo)記的角色,封裝到類的類型中。兩方面的原因促成了 Clo neable in terface的存在。首先,可能有一個(gè)上溯造型 句柄指向一個(gè)基礎(chǔ)類型, 而且不知道它是否真的能克隆那個(gè)對象。 在這種情況下, 可用instanceof關(guān)

18、鍵字(第11章有介紹)調(diào)查句柄是否確實(shí)同一個(gè)能克隆的對象 連接:if(myHandle instanceof Cloneable) / .第二個(gè)原因 是考慮到我 們可能不愿所有對象 類型都能克隆。 所以 Object.clone()會(huì)驗(yàn)證一個(gè)類是否真的是實(shí)現(xiàn)了Cloneable接口。若答案是否定的,則“擲”出一個(gè) CloneNotSupportedException違例。所以在一般情況下,我們必 須將“ implement Cloneable 作”為對克隆能力提供支持的一部分。12.2.4 成功的克隆理解了實(shí)現(xiàn)clone()方法背后的所有細(xì)節(jié)后,便可創(chuàng)建出能方便復(fù)制的類,以 便提供了一個(gè)本地副

19、本:550-551 頁程序不管怎樣,clone()必須能夠訪問,所以必須將其設(shè)為public(公共的)。其次, 作為clone()的初期行動(dòng),應(yīng)調(diào)用 clone()的基礎(chǔ)類版本。這里調(diào)用的 clone()是 Object內(nèi)部預(yù)先定義好的。之所以能調(diào)用它,是由于它具有 protected (受到保護(hù) 的)屬性,所以能在衍生的類里訪問。Object.clo ne()會(huì)檢查原先的對象有多大,再為新對象騰出足夠多的內(nèi)存,將 所有二進(jìn)制位從原來的對象復(fù)制到新對象。這叫作“按位復(fù)制” ,而且按一般的 想法,這個(gè)工作應(yīng)該是由clone()方法來做的。但在Object.clone()正式開始操作前, 首先會(huì)檢

20、查一個(gè)類是否 Cloneable,即是否具有克隆能力一一換言之,它是否實(shí) 現(xiàn) 了 Cloneable 接 口 。 若 未 實(shí) 現(xiàn) , Object.clone() 就 擲 出 一 個(gè) CloneNotSupportedException 違例,指出我們不能克隆它。因此,我們最好用一 個(gè)try-catch塊將對super.clone(的調(diào)用代碼包圍(或圭寸裝)起來,試圖捕獲一個(gè) 應(yīng)當(dāng)永不出現(xiàn)的違例(因?yàn)檫@里確實(shí)已實(shí)現(xiàn)了Clo neable接口)。在LocalCopy中,兩個(gè)方法g()和f()揭示出兩種參數(shù)傳遞方法間的差異。其 中,g()演示的是按引用傳遞,它會(huì)修改外部對象,并返回對那個(gè)外部對象的

21、一 個(gè)引用。而f()是對自變量進(jìn)行克隆,所以將其分離出來,并讓原來的對象保持 獨(dú)立。隨后,它繼續(xù)做它希望的事情。甚至能返回指向這個(gè)新對象的一個(gè)句柄, 而且不會(huì)對原來的對象產(chǎn)生任何副作用。注意下面這個(gè)多少有些古怪的語句:v = (MyObject)v.clone(); 它的作用正是創(chuàng)建一個(gè)本地副本。為避免被這樣的一個(gè)語句搞混淆,記住這 種相當(dāng)奇怪的編碼形式在 Java 中是完全允許的,因?yàn)橛幸粋€(gè)名字的所有東西實(shí) 際都是一個(gè)句柄。 所以句柄 v 用于克隆一個(gè)它所指向的副本, 而且最終返回指向 基礎(chǔ)類型Object的一個(gè)句柄(因?yàn)樗?Object.clone()中是那樣被定義的),隨后 必須將其造

22、型為正確的類型。在main()中,兩種不同參數(shù)傳遞方式的區(qū)別在于它們分別測試了一個(gè)不同的 方法。輸出結(jié)果如下:552 頁程序大家要記住這樣一個(gè)事實(shí):Java對“是否等價(jià)”的測試并不對所比較對象的 內(nèi)部進(jìn)行檢查, 從而核實(shí)它們的值是否相同。 =和!=運(yùn)算符只是簡單地對比句柄 的內(nèi)容。若句柄內(nèi)的地址相同, 就認(rèn)為句柄指向同樣的對象, 所以認(rèn)為它們是“等 價(jià)”的。所以運(yùn)算符真正檢測的是 “由于別名問題, 句柄是否指向同一個(gè)對象?”12.2.5 Object.clo ne()的效果調(diào)用Object.clone()時(shí),實(shí)際發(fā)生的是什么事情呢?當(dāng)我們在自己的類里覆蓋 clo ne()時(shí),什么東西對于 su

23、per.clo ne(來說是最關(guān)鍵的呢?根類中的 clone()方法 負(fù)責(zé)建立正確的存儲(chǔ)容量, 并通過“按位復(fù)制” 將二進(jìn)制位從原始對象中復(fù)制到 新對象的存儲(chǔ)空間。 也就是說, 它并不只是預(yù)留存儲(chǔ)空間以及復(fù)制一個(gè)對象 實(shí)際需要調(diào)查出欲復(fù)制之對象的準(zhǔn)確大小, 然后復(fù)制那個(gè)對象。 由于所有這些工 作都是在由根類定義之clone()方法的內(nèi)部代碼中進(jìn)行的(根類并不知道要從自己 這里繼承出去什么),所以大家或許已經(jīng)猜到,這個(gè)過程需要用 RTTI 判斷欲克 隆的對象的實(shí)際大小。采取這種方式,clone()方法便可建立起正確數(shù)量的存儲(chǔ)空 間,并對那個(gè)類型進(jìn)行正確的按位復(fù)制。不管我們要做什么,克隆過程的第一

24、個(gè)部分通常都應(yīng)該是調(diào)用super.clone()。通過進(jìn)行一次準(zhǔn)確的復(fù)制,這樣做可為后續(xù)的克隆進(jìn)程建立起一個(gè)良好的基礎(chǔ)。 隨后,可采取另一些必要的操作,以完成最終的克隆。為確切了解其他操作是什么,首先要正確理解Object.clone()為我們帶來了什 么。特別地,它會(huì)自動(dòng)克隆所有句柄指向的目標(biāo)嗎?下面這個(gè)例子可完成這種形 式的檢測:553-554頁程序一條Snake (蛇)由數(shù)段構(gòu)成,每一段的類型都是Snaka所以,這是一個(gè)一段段鏈接起來的列表。 所有段都是以循環(huán)方式創(chuàng)建的, 每做好一段, 都會(huì)使第 一個(gè)構(gòu)建器參數(shù)的值遞減, 直至最終為零。而為給每段賦予一個(gè)獨(dú)一無二的標(biāo)記, 第二個(gè)參數(shù)(一個(gè)

25、Char)的值在每次循環(huán)構(gòu)建器調(diào)用時(shí)都會(huì)遞增。in creme nt()方法的作用是循環(huán)遞增每個(gè)標(biāo)記,使我們能看到發(fā)生的變化;而 toString 則循環(huán)打印出每個(gè)標(biāo)記。輸出如下:554 頁中程序這意味著只有第一段才是由Object.clo ne()復(fù)制的,所以此時(shí)進(jìn)行的是一種“淺層復(fù)制”。若希望復(fù)制整條蛇即進(jìn)行“深層復(fù)制”必須在被覆蓋的 clo ne()里采取附加的操作。通??稍趶囊粋€(gè)能克隆的類里調(diào)用 super.clone()以確保所有基礎(chǔ)類行動(dòng)(包 括Object.clone()能夠進(jìn)行。隨著是為對象內(nèi)每個(gè)句柄都明確調(diào)用一個(gè)clone();否則那些句柄會(huì)別名變成原始對象的句柄。 構(gòu)建器的

26、調(diào)用也大致相同首先構(gòu) 造基礎(chǔ)類,然后是下一個(gè)衍生的構(gòu)建器 , 以此類推, 直到位于最深層的衍生構(gòu) 建器。區(qū)別在于clone()并不是個(gè)構(gòu)建器,所以沒有辦法實(shí)現(xiàn)自動(dòng)克隆。為了克隆, 必須由自己明確進(jìn)行。12.2.6 克隆合成對象試圖深層復(fù)制合成對象時(shí)會(huì)遇到一個(gè)問題。必須假定成員對象中的 clone() 方法也能依次對自己的句柄進(jìn)行深層復(fù)制, 以此類推。這使我們的操作變得復(fù)雜。 為了能正常實(shí)現(xiàn)深層復(fù)制, 必須對所有類中的代碼進(jìn)行控制, 或者至少全面掌握 深層復(fù)制中需要涉及的類,確保它們自己的深層復(fù)制能正確進(jìn)行。下面這個(gè)例子總結(jié)了面對一個(gè)合成對象進(jìn)行深層復(fù)制時(shí)需要做哪些事情:555-556頁程序De

27、pthReading和TemperatureReadin國E常相似;它們都只包含了基本數(shù)據(jù)類 型。所以clone()方法能夠非常簡單:調(diào)用super.clone(并返回結(jié)果即可。注意兩 個(gè)類使用的clone()代碼是完全一致的。OceanReading是由 DepthReading和 TemperatureReading對象合并而成的。為 了對其進(jìn)行深層復(fù)制,clone()必須同時(shí)克隆 OceanReading內(nèi)的句柄。為達(dá)到這 個(gè)目標(biāo),super.clone(的結(jié)果必須造型成一個(gè) OceanReading對象(以便訪問depth 和 temperature句柄)。12.2.7用Vector進(jìn)

28、行深層復(fù)制下面讓我們復(fù)習(xí)一下本章早些時(shí)候提出的 Vector例子。這一次Int2類是可以 克隆的,所以能對Vector進(jìn)行深層復(fù)制:557-558頁程序Int3自Int2繼承而來,并添加了一個(gè)新的基本類型成員int j。大家也許認(rèn)為自己需要再次覆蓋clone(),以確保j得到復(fù)制,但實(shí)情并非如此。將Int2的clone() 當(dāng)作Int3的clone()調(diào)用時(shí),它會(huì)調(diào)用 Object.clone(),判斷出當(dāng)前操作的是Int3, 并復(fù)制 Int3 內(nèi)的所有二進(jìn)制位。 只要沒有新增需要克隆的句柄, 對 Object.clone() 的一個(gè)調(diào)用就能完成所有必要的復(fù)制一一無論clo ne()是在層次結(jié)

29、構(gòu)多深的一級(jí)定義的。至此,大家可以總結(jié)出對 Vector進(jìn)行深層復(fù)制的先決條件:在克隆了 Vector 后,必須在其中遍歷,并克隆由 Vector指向的每個(gè)對象。為了對 Hashtable (散 列表)進(jìn)行深層復(fù)制,也必須采取類似的處理。這個(gè)例子剩余的部分顯示出克隆已實(shí)際進(jìn)行證據(jù)就是在克隆了對象以后,可以自由改變它,而原來那個(gè)對象不受任何影響。12.2.8 通過序列化進(jìn)行深層復(fù)制若研究一下第 10章介紹的那個(gè) Java 1.1對象序列化示例,可能發(fā)現(xiàn)若在一個(gè) 對象序列化以后再撤消對它的序列化, 或者說進(jìn)行裝配, 那么實(shí)際經(jīng)歷的正是一 個(gè)“克隆”的過程。那么為什么不用序列化進(jìn)行深層復(fù)制呢?下面這

30、個(gè)例子通過計(jì)算執(zhí)行時(shí)間 對比了這兩種方法:559-560 頁程序其中, Thing2 和 Thing4 包含了成員對象,所以需要進(jìn)行一些深層復(fù)制。一 個(gè)有趣的地方是盡管 Serializable 類很容易設(shè)置,但在復(fù)制它們時(shí)卻要做多得多 的工作??寺∩婕暗酱罅康念愒O(shè)置工作, 但實(shí)際的對象復(fù)制是相當(dāng)簡單的。 結(jié)果 很好地說明了一切。下面是幾次運(yùn)行分別得到的結(jié)果:的確561 頁上程序除了序列化和克隆之間巨大的時(shí)間差異以外, 我們也注意到序列化技術(shù)的運(yùn) 行結(jié)果并不穩(wěn)定,而克隆每一次花費(fèi)的時(shí)間都是相同的。12.2.9 使克隆具有更大的深度若新建一個(gè)類,它的基礎(chǔ)類會(huì)默認(rèn)為 Object,并默認(rèn)為不具備克

31、隆能力(就 象在下一節(jié)會(huì)看到的那樣) 。只要不明確地添加克隆能力,這種能力便不會(huì)自動(dòng) 產(chǎn)生。但我們可以在任何層添加它,然后便可從那個(gè)層開始向下具有克隆能力。 如下所示:561-562頁程序添加克隆能力之前,編譯器會(huì)阻止我們的克隆嘗試。一旦在Scien tist里添加了克隆能力,那么Scientist以及它的所有“后裔”都可以克隆。12.2.10 為什么有這個(gè)奇怪的設(shè)計(jì) 之所以感覺這個(gè)方案的奇特,因?yàn)樗聦?shí)上的確如此。也許大家會(huì)奇怪它為 什么要象這樣運(yùn)行, 而該方案背后的真正含義是什么呢?后面講述的是一個(gè)未獲 證實(shí)的故事大概是由于圍繞 Java 的許多買賣使其成為一種設(shè)計(jì)優(yōu)良的語言 但確實(shí)要花許

32、多口舌才能講清楚這背后發(fā)生的所有事情。最初,Java只是作為一種用于控制硬件的語言而設(shè)計(jì),與因特網(wǎng)并沒有絲毫 聯(lián)系。象這樣一類面向大眾的語言一樣, 其意義在于程序員可以對任意一個(gè)對象 進(jìn)行克隆。這樣一來,clone()就放置在根類Object里面,但因?yàn)樗且环N公用方 式,因而我們通常能夠?qū)θ我庖粋€(gè)對象進(jìn)行克隆。 看來這是最靈活的方式了, 畢 竟它不會(huì)帶來任何害處。正當(dāng) Java 看起來象一種終級(jí)因特網(wǎng)程序設(shè)計(jì)語言的時(shí)候,情況卻發(fā)生了變化。突然地,人們提出了安全問題,而且理所當(dāng)然,這些問題與使用對象有關(guān), 我們不愿望任何人克隆自己的保密對象。所以我們最后看到的是為原來那個(gè)簡 單、直觀的方案添加

33、的大量補(bǔ)?。篶lone()在Object里被設(shè)置成“ protected?!北仨殞⑵涓采w,并使用“ implement Cloneable ,同時(shí)解決違例的問題。只有在準(zhǔn)備調(diào)用Object的clone()方法時(shí),才沒有必要使用 Cloneable接口, 因?yàn)槟莻€(gè)方法會(huì)在運(yùn)行期間得到檢查,以確保我們的類實(shí)現(xiàn)了Cloneable。但為了保持連貫性(而且由于 Cloneable 無論如何都是空的),最好還是由自己實(shí)現(xiàn) Cloneable。12.3 克隆的控制為消除克隆能力,大家也許認(rèn)為只需將clone()方法簡單地設(shè)為private(私有) 即可,但這樣是行不通的, 因?yàn)椴荒懿捎靡粋€(gè)基礎(chǔ)類方法, 并

34、使其在衍生類中更 “私有”。所以事情并沒有這么簡單。此外,我們有必要控制一個(gè)對象是否能夠 克隆。對于我們設(shè)計(jì)的一個(gè)類,實(shí)際有許多種方案都是可以采取的:(1) 保持中立,不為克隆做任何事情。也就是說,盡管不可對我們的類克隆, 但從它繼承的一個(gè)類卻可根據(jù)實(shí)際情況決定克隆。只有Object.clone()要對類中的 字段進(jìn)行某些合理的操作時(shí),才可以作這方面的決定。支持clone(),采用實(shí)現(xiàn)Cloneable (可克隆)能力的標(biāo)準(zhǔn)操作,并覆蓋 clone()。在被覆蓋的clone()中,可調(diào)用super.clone(,并捕獲所有違例(這樣可 使clone()不“擲”出任何違例)。(3) 有條件地支持

35、克隆。若類容納了其他對象的句柄,而那些對象也許能夠 克隆(集合類便是這樣的一個(gè)例子) ,就可試著克隆擁有對方句柄的所有對象; 如果它們“擲”出了違例,只需讓這些違例通過即可。舉個(gè)例子來說,假設(shè)有一 個(gè)特殊的Vector,它試圖克隆自己容納的所有對象。編寫這樣的一個(gè)Vector時(shí),并不知道客戶程序員會(huì)把什么形式的對象置入這個(gè) Vector 中,所以并不知道它 們是否真的能夠克隆。不實(shí)現(xiàn)Cloneable(),但是將clone()覆蓋成protected,使任何字段都具有 正確的復(fù)制行為。這樣一來,從這個(gè)類繼承的所有東西都能覆蓋 cione(),并調(diào)用 super.clo ne(來產(chǎn)生正確的復(fù)制行

36、為。注意在我們實(shí)現(xiàn)方案里,可以而且應(yīng)該調(diào) 用super.clone(即使那個(gè)方法本來預(yù)期的是一個(gè) Cloneable對象(否則會(huì)擲出一個(gè)違例),因?yàn)闆]有人會(huì)在我們這種類型的對象上直接調(diào)用它。它只有通過一 個(gè)衍生類調(diào)用;對那個(gè)衍生類來說,如果要保證它正常工作,需實(shí)現(xiàn)Cloneable。(5)不實(shí)現(xiàn)Cloneable來試著防止克隆,并覆蓋clone(),以產(chǎn)生一個(gè)違例。為 使這一設(shè)想順利實(shí)現(xiàn), 只有令從它衍生出來的任何類都調(diào)用重新定義后的 clone() 里的 suepr.clone()。將類設(shè)為final,從而防止克隆。若clone()尚未被我們的任何一個(gè)上級(jí)類 覆蓋,這一設(shè)想便不會(huì)成功。若已被

37、覆蓋,那么再一次覆蓋它,并“擲”出一個(gè) CloneNotSupportedException (克隆不支持)違例。為擔(dān)??寺”唤?,將類設(shè)為 final 是唯一的辦法。 除此以外,一旦涉及保密對象或者遇到想對創(chuàng)建的對象數(shù)量 進(jìn)行控制的其他情況,應(yīng)該將所有構(gòu)建器都設(shè)為private,并提供一個(gè)或更多的特 殊方法來創(chuàng)建對象。 采用這種方式, 這些方法就可以限制創(chuàng)建的對象數(shù)量以及它 們的創(chuàng)建條件一一一種特殊情況是第16章要介紹的singleton (獨(dú)子)方案。面這個(gè)例子總結(jié)了克隆的各種實(shí)現(xiàn)方法,然后在層次結(jié)構(gòu)中將其“關(guān)閉”564-565頁程序第一個(gè)類 Ordinary 代表著大家在本書各處最常見到

38、的類: 不支持克隆, 但在Ordinary 對象的句便不能判斷它到底它正式應(yīng)用以后,卻也不禁止對其克隆。但假如有一個(gè)指向 柄,而且那個(gè)對象可能是從一個(gè)更深的衍生類上溯造型來的, 能不能克隆WrongClone 類 揭 示 了 實(shí) 現(xiàn) 克 隆 的 一 種 不 正 確 途 徑 。 它 確 實(shí) 覆 蓋 了 Object.clone(),并將那個(gè)方法設(shè)為public,但卻沒有實(shí)現(xiàn)Cloneableo所以一旦發(fā) 出對super.clone(的調(diào)用(由于對Object.clone()的一個(gè)調(diào)用造成的),便會(huì)無情地 擲出 CloneNotSupportedException違例。在IsCloneable中,

39、大家看到的才是進(jìn)行克隆的各種正確行動(dòng):先覆蓋clone(),并實(shí)現(xiàn)了 Cloneableo但是,這個(gè)clone()方法以及本例的另外幾個(gè)方法并不捕獲 CloneNotSupportedException 違例,而是任由它通過,并傳遞給調(diào)用者。隨后, 調(diào)用者必須用一個(gè)try-catch代碼塊把它包圍起來。在我們自己的clone()方法中,通常需要在cione()內(nèi)部捕獲CloneNotSupportedException違例,而不是任由它通 過。正如大家以后會(huì)理解的那樣,對這個(gè)例子來說,讓它通過是最正確的做法。類NoMore試圖按照J(rèn)ava設(shè)計(jì)者打算的那樣“關(guān)閉”克?。涸谘苌恈lone() 中

40、,我們擲出 CloneNotSupportedException違例。TryMore 類中的 clone()方法正 確地調(diào)用super.clone(),并解析成NoMore.clone(),后者擲出一個(gè)違例并禁止克 隆。但在已被覆蓋的clone()方法中,假若程序員不遵守調(diào)用super.clone(的“正確”方法,又會(huì)出現(xiàn)什么情況呢?在Back On中,大家可看到實(shí)際會(huì)發(fā)生什么。這個(gè)類用一個(gè)獨(dú)立的方法duplicate()制作當(dāng)前對象的一個(gè)副本,并在clo ne()內(nèi)部 調(diào)用這個(gè)方法,而不是調(diào)用 super.clone(違例永遠(yuǎn)不會(huì)產(chǎn)生,而且新類是可以 克隆的。因此,我們不能依賴“擲”出一個(gè)違

41、例的方法來防止產(chǎn)生一個(gè)可克隆的 類。唯一安全的方法在 ReallyNoMore中得到了演示,它設(shè)為final,所以不可繼 承。這意味著假如clone()在final類中擲出了一個(gè)違例,便不能通過繼承來進(jìn)行 修改,并可有效地禁止克隆(不能從一個(gè)擁有任意繼承級(jí)數(shù)的類中明確調(diào)用 Object.clone();只能調(diào)用super.clone()它只可訪問直接基礎(chǔ)類)。因此,只要制 作一些涉及安全問題的對象,就最好把那些類設(shè)為 final。在類CheckCloneable中,我們看到的第一個(gè)類是tryToClone(),它能接納任 何Ordinary對象,并用instanceof檢查它是否能夠克隆。若答

42、案是肯定的,就將 對象造型成為一個(gè)IsCloneable,調(diào)用clone(),并將結(jié)果造型回Ordinary,最后捕 獲有可能產(chǎn)生的任何違例。 請注意用運(yùn)行期類型鑒定(見第 11 章)打印出類名, 使自己看到發(fā)生的一切情況。在main()中,我們創(chuàng)建了不同類型的 Ordinary對象,并在數(shù)組定義中上溯造 型成為Ordinary。在這之后的頭兩行代碼創(chuàng)建了一個(gè)純粹的Ordinary對象,并試圖對其克隆。然而,這些代碼不會(huì)得到編譯,因?yàn)閏lone()是Object中的一個(gè)protected (受到保護(hù)的)方法。代碼剩余的部分將遍歷數(shù)組,并試著克隆每個(gè)對 象,分別報(bào)告它們的成功或失敗。輸出如下:5

43、67-568頁程序總之,如果希望一個(gè)類能夠克隆,那么:(1) 實(shí)現(xiàn) Cloneable接口(2) 覆蓋 clone() 在自己的clone()中調(diào)用super.clone() 在自己的clone()中捕獲違例 這一系列步驟能達(dá)到最理想的效果。12.3.1 副本構(gòu)建器 克隆看起來要求進(jìn)行非常復(fù)雜的設(shè)置,似乎還該有另一種替代方案。一個(gè)辦法是制作特殊的構(gòu)建器,令其負(fù)責(zé)復(fù)制一個(gè)對象。在 C+中,這叫作“副本構(gòu)建 器”剛開始的時(shí)候,這好象是一種非常顯然的解決方案 (如果你是C+程序員, 這個(gè)方法就更顯親切) 。下面是一個(gè)實(shí)際的例子:568-571 頁程序這個(gè)例子第一眼看上去顯得有點(diǎn)奇怪。不同水果的質(zhì)量肯

44、定有所區(qū)別,但為 什么只是把代表那些質(zhì)量的數(shù)據(jù)成員直接置入Fruit (水果)類?有兩方面可能的原因。第一個(gè)是我們可能想簡便地插入或修改質(zhì)量。 注意 Fruit 有一個(gè) protected (受到保護(hù)的)addQualities()方法,它允許衍生類來進(jìn)行這些插入或修改操作(大 家或許會(huì)認(rèn)為最合乎邏輯的做法是在Fruit中使用一個(gè)protected構(gòu)建器,用它獲取 FruitQualities 參數(shù),但構(gòu)建器不能繼承, 所以不可在第二級(jí)或級(jí)數(shù)更深的類中 使用它)。通過將水果的質(zhì)量置入一個(gè)獨(dú)立的類,可以得到更大的靈活性,其中 包括可以在特定 Fruit 對象的存在期間中途更改質(zhì)量。之所以將 Fr

45、uitQualities 設(shè)為一個(gè)獨(dú)立的對象, 另一個(gè)原因是考慮到我們有時(shí) 希望添加新的質(zhì)量,或者通過繼承與多形性改變行為。注意對Gree nZebra來說(這實(shí)際是西紅柿的一類我已栽種成功,它們簡直令人難以置信) ,構(gòu)建器 會(huì)調(diào)用 addQualities(),并為其傳遞一個(gè)ZebraQualities對象。該對象是從 FruitQualities衍生出來的,所以能與基礎(chǔ)類中的FruitQualities句柄聯(lián)系在一起。當(dāng)然,一旦GreenZebra使用FruitQualities,就必須將其下溯造型成為正確的類型 (就象evaluate。中展示的那樣),但它肯定知道類型是ZebraQual

46、ities。大家也看到有一個(gè)Seed (種子)類,F(xiàn)ruit (大家都知道,水果含有自己的種 子)包含了一個(gè)Seed數(shù)組。最后,注意每個(gè)類都有一個(gè)副本構(gòu)建器,而且每個(gè)副本構(gòu)建器都必須關(guān)心為 基礎(chǔ)類和成員對象調(diào)用副本構(gòu)建器的問題,從而獲得“深層復(fù)制”的效果。對副 本構(gòu)建器的測試是在CopyConstructor類內(nèi)進(jìn)行的。方法 ripen()需要獲取一個(gè)Tomato參數(shù),并對其執(zhí)行副本構(gòu)建工作,以便復(fù)制對象:t = new Tomato(t);而slice()需要獲取一個(gè)更常規(guī)的Fruit對象,而且對它進(jìn)行復(fù)制:f = new Fruit(f);它們都在main()中伴隨不同種類的Fruit進(jìn)行

47、測試。下面是輸出結(jié)果:572 頁上程序從中可以看出一個(gè)問題。在slice()內(nèi)部對Tomato進(jìn)行了副本構(gòu)建工作以后,結(jié)果便不再是一個(gè) Tomato 對象,而只是一個(gè) Fruit 。它已丟失了作為一個(gè) Tomato (西紅柿)的所有特征。此外,如果采用一個(gè)GreenZebra ripen()和slice()會(huì)把它分別轉(zhuǎn)換成一個(gè)Tomato和一個(gè)Fruit。所以非常不幸,假如想制作對象的一個(gè) 本地副本,Java中的副本構(gòu)建器便不是特別適合我們。1.為什么在C+的作用比在Java中大?副本構(gòu)建器是C+的一個(gè)基本構(gòu)成部分,因?yàn)樗茏詣?dòng)產(chǎn)生對象的一個(gè)本地 副本。但前面的例子確實(shí)證明了它不適合在 Jav

48、a中使用,為什么呢?在Java中, 我們操控的一切東西都是句柄,而在C+中,卻可以使用類似于句柄的東西,也 能直接傳遞對象。這時(shí)便要用到C+的副本構(gòu)建器:只要想獲得一個(gè)對象,并按 值傳遞它,就可以復(fù)制對象。所以它在C+里能很好地工作,但應(yīng)注意這套機(jī)制 在Java里是很不通的,所以不要用它。12.4 只讀類盡管在一些特定的場合,由 clo ne()產(chǎn)生的本地副本能夠獲得我們希望的結(jié) 果,但程序員(方法的作者)不得不親自禁止別名處理的副作用。假如想制作一 個(gè)庫,令其具有常規(guī)用途, 但卻不能擔(dān)保它肯定能在正確的類中得以克隆, 這時(shí) 又該怎么辦呢?更有可能的一種情況是, 假如我們想讓別名發(fā)揮積極的作用

49、 禁止不必要的對象復(fù)制但卻不希望看到由此造成的副作用, 那么又該如何處 理呢?一個(gè)辦法是創(chuàng)建“不變對象” ,令其從屬于只讀類??啥x一個(gè)特殊的類, 使其中沒有任何方法能造成對象內(nèi)部狀態(tài)的改變。 在這樣的一個(gè)類中, 別名處理 是沒有問題的。 因?yàn)槲覀冎荒茏x取內(nèi)部狀態(tài), 所以當(dāng)多處代碼都讀取相同的對象 時(shí),不會(huì)出現(xiàn)任何副作用。作為“不變對象” 一個(gè)簡單例子,Java的標(biāo)準(zhǔn)庫包含了“封裝器” (wrapper) 類,可用于所有基本數(shù)據(jù)類型。 大家可能已發(fā)現(xiàn)了這一點(diǎn), 如果想在一個(gè)象 Vector (只采用 Object 句柄)這樣的集合里保存一個(gè) int 數(shù)值,可以將這個(gè) int 封裝到 標(biāo)準(zhǔn)庫的I

50、nteger類內(nèi)部。如下所示:573頁中程序Integer 類(以及基本的“封裝器”類)用簡單的形式實(shí)現(xiàn)了“不變性” :它 們沒有提供可以修改對象的方法。若確實(shí)需要一個(gè)容納了基本數(shù)據(jù)類型的對象,并想對基本數(shù)據(jù)類型進(jìn)行修 改,就必須親自創(chuàng)建它們。幸運(yùn)的是,操作非常簡單:573-574頁程序注意 n 在這里簡化了我們的編碼。 若默認(rèn)的初始化為零已經(jīng)足夠(便不需要構(gòu)建器) ,而且不用考慮把它打印 出來(便不需要toString),那么IntValue甚至還能更加簡單。如下所示:class IntValue int n; 將元素取出來,再對其進(jìn)行造型,這多少顯得有些笨拙,但那是Vector的問題,不是

51、IntValue的錯(cuò)。12.4.1 創(chuàng)建只讀類完全可以創(chuàng)建自己的只讀類,下面是個(gè)簡單的例子:574-575頁程序所有數(shù)據(jù)都設(shè)為private,可以看到?jīng)]有任何public方法對數(shù)據(jù)作出修改。事 實(shí)上,確實(shí)需要修改一個(gè)對象的方法是quadruple。,但它的作用是新建一個(gè)Immutable1 對象,初始對象則是原封未動(dòng)的。方法f()需要取得一個(gè)Immutablel對象,并對其采取不同的操作,而 main() 的輸出顯示出沒有對x作任何修改。因此,x對象可別名處理許多次,不會(huì)造成 任何傷害,因?yàn)楦鶕?jù) Immutable1 類的設(shè)計(jì),它能保證對象不被改動(dòng)。12.4.2 一“成不變”的弊端 從表面看,

52、不變類的建立似乎是一個(gè)好方案。但是,一旦真的需要那種新類 型的一個(gè)修改的對象, 就必須辛苦地進(jìn)行新對象的創(chuàng)建工作, 同時(shí)還有可能涉及 更頻繁的垃圾收集。對有些類來說,這個(gè)問題并不是很大。但對其他類來說(比 如 String 類),這一方案的代價(jià)顯得太高了。為解決這個(gè)問題,我們可以創(chuàng)建一個(gè)“同志”類,并使其能夠修改。以后只 要涉及大量的修改工作, 就可換為使用能修改的同志類。 完事以后, 再切換回不 可變的類。因此,上例可改成下面這個(gè)樣子:575-577頁程序和往常一樣, Immutable2 包含的方法保留了對象不可變的特征,只要涉及修 改,就創(chuàng)建新的對象。完成這些操作的是add()和 mul

53、tiply。方法。同志類叫作Mutable,它也含有add()和multiply()方法。但這些方法能夠修改 Mutable對象, 而不是新建一個(gè)。除此以外, Mutable 的一個(gè)方法可用它的數(shù)據(jù)產(chǎn)生一個(gè) Immutable2 對象,反之亦然。兩個(gè)靜態(tài)方法modify1()和modify2()揭示出獲得同樣結(jié)果的兩種不同方法。 在modify1()中,所有工作都是在Immutable2類中完成的,我們可看到在進(jìn)程中 創(chuàng)建了四個(gè)新的Immutable2對象(而且每次重新分配了 val,前一個(gè)對象就成為 垃圾)。在方法modify2()中,可看到它的第一個(gè)行動(dòng)是獲取Immutable2 y,然后

54、從中 生成一個(gè)Mutable (類似于前面對clone()的調(diào)用,但這一次創(chuàng)建了一個(gè)不同類型 的對象)。隨后,用Mutable對象進(jìn)行大量修改操作,同時(shí)用不著新建許多對象。 最后,它切換回Immutable2。在這里,我們只創(chuàng)建了兩個(gè)新對象( Mutable和 Immutable2 的結(jié)果),而不是四個(gè)。這一方法特別適合在下述場合應(yīng)用:(1) 需要不可變的對象,而且(2) 經(jīng)常需要進(jìn)行大量修改,或者(3) 創(chuàng)建新的不變對象代價(jià)太高12.4.3 不變字串 請觀察下述代碼:577-578頁程序q傳遞進(jìn)入upcase()時(shí),它實(shí)際是q的句柄的一個(gè)副本。該句柄連接的對象 實(shí)際只在一個(gè)統(tǒng)一的物理位置處。

55、句柄四處傳遞的時(shí)候,它的句柄會(huì)得到復(fù)制。若觀察對upcase()的定義,會(huì)發(fā)現(xiàn)傳遞進(jìn)入的句柄有一個(gè)名字 s,而且該名字 只有在upcase(執(zhí)行期間才會(huì)存在。upcase(完成后,本地句柄s便會(huì)消失,而 upcase(返回結(jié)果一一還是原來那個(gè)字串,只是所有字符都變成了大寫。當(dāng)然, 它返回的實(shí)際是結(jié)果的一個(gè)句柄。 但它返回的句柄最終是為一個(gè)新對象的, 同時(shí) 原來的 q 并未發(fā)生變化。所有這些是如何發(fā)生的呢?1. 隱式常數(shù)若使用下述語句:String s = asdf;String x = Stringer.upcase(s);那么真的希望upcase()方法改變自變量或者參數(shù)嗎?我們通常是不愿意

56、的, 因?yàn)樽鳛樘峁┙o方法的一種信息, 自變量一般是拿給代碼的讀者看的, 而不是讓 他們修改。這是一個(gè)相當(dāng)重要的保證,因?yàn)樗勾a更易編寫和理解。為了在C+中實(shí)現(xiàn)這一保證,需要一個(gè)特殊關(guān)鍵字的幫助:const。利用這個(gè) 關(guān)鍵字,程序員可以保證一個(gè)句柄(C+叫“指針”或者“引用”)不會(huì)被用來 修改原始的對象。但這樣一來,C+程序員需要用心記住在所有地方都使用 const。 這顯然易使人混淆,也不容易記住。2. 覆蓋+和 StringBuffer利用前面提到的技術(shù), String 類的對象被設(shè)計(jì)成“不可變” 。若查閱聯(lián)機(jī)文檔 中關(guān)于 String 類的內(nèi)容(本章稍后還要總結(jié)它) ,就會(huì)發(fā)現(xiàn)類中能夠修

57、改 String 的每個(gè)方法實(shí)際都創(chuàng)建和返回了一個(gè)嶄新的String對象,新對象里包含了修改過的信息原來的String是原圭寸未動(dòng)的。因此,Java里沒有與C+的const對應(yīng)的特性可用來讓編譯器支持對象的不可變能力。 若想獲得這一能力, 可以自行設(shè) 置,就象 String 那樣。由于 String 對象是不可變的,所以能夠根據(jù)情況對一個(gè)特定的 String 進(jìn)行多 次別名處理。 因?yàn)樗侵蛔x的, 所以一個(gè)句柄不可能會(huì)改變一些會(huì)影響其他句柄 的東西。因此,只讀對象可以很好地解決別名問題。通過修改產(chǎn)生對象的一個(gè)嶄新版本,似乎可以解決修改對象時(shí)的所有問題, 就象 String 那樣。但對某些操作來講,這種方法的效率并不高。一個(gè)典型的例子 便是為String對象覆蓋的運(yùn)算符“ +”。“覆蓋”意味著在與一個(gè)特定的類使用時(shí), 它的含義已發(fā)生了變化(用于 String的“+”和“+二”是Java中能被覆蓋的唯一運(yùn) 算符,Java不允許程序員覆蓋其他任何運(yùn)算符一一注釋)。:C+允許程序員隨意覆蓋運(yùn)算符。由于這通常是一個(gè)復(fù)雜的過程(參見Thinking in C+,Prentice-Hall于1995年出版),所以Java的設(shè)計(jì)者認(rèn)定它是 一種“糟糕”的特性,決定不在 Java中采用。但具有諷剌意味的是,運(yùn)算符的覆蓋在Java中要比在C+中容易得多針對 Str

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論