版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、第 12 章 傳遞和返回對(duì)象 到目前為止,讀者應(yīng)對(duì)對(duì)象的“傳遞”有了一個(gè)較為深刻的認(rèn)識(shí),記住實(shí)際傳遞 的只是一個(gè)句柄。在許多程序設(shè)計(jì)語言中,我們可用語言的“普通”方式到處傳遞對(duì)象,而且大多 數(shù)時(shí)候都不會(huì)遇到問題。但有些時(shí)候卻不得不采取一些非常做法,使得情況突然 變得稍微復(fù)雜起來(在C+中則是變得非常復(fù)雜)。Java亦不例外,我們十分有 必要準(zhǔn)確認(rèn)識(shí)在對(duì)象傳遞和賦值時(shí)所發(fā)生的一切。這正是本章的宗旨。若讀者是從某些特殊的程序設(shè)計(jì)環(huán)境中轉(zhuǎn)移過來的,那么一般都會(huì)問到:“Java 有指針嗎?”有些人認(rèn)為指針的操作很困難,而且十分危險(xiǎn),所以一廂情愿地認(rèn) 為它沒有好處。同時(shí)由于Java有如此好的口碑,所以應(yīng)
2、該很輕易地免除自己以 前編程中的麻煩,其中不可能夾帶有指針這樣的“危險(xiǎn)品”。然而準(zhǔn)確地說Java 是有指針的!事實(shí)上,Java中每個(gè)對(duì)象(除基本數(shù)據(jù)類型以外)的標(biāo)識(shí)符都屬 于指針的一種。但它們的使用受到了嚴(yán)格的限制和防范,不僅編譯器對(duì)它們有 “戒心”,運(yùn)行期系統(tǒng)也不例外?;蛘邠Q從另一個(gè)角度說,Java有指針,但沒 有傳統(tǒng)指針的麻煩。我曾一度將這種指針叫做“句柄”,但你可以把它想像成“安全指針”。和預(yù)備學(xué)校為學(xué)生提供的安全剪刀類似除非特別有意,否則 不會(huì)傷著自己,只不過有時(shí)要慢慢來,要習(xí)慣一些沉悶的工作。傳遞句柄 將句柄傳遞進(jìn)入一個(gè)方法時(shí),指向的仍然是相同的對(duì)象。一個(gè)簡(jiǎn)單的實(shí)驗(yàn)可以證 明這一點(diǎn)(
3、若執(zhí)行這個(gè)程序時(shí)有麻煩,請(qǐng)參考第 3 章 3.1.2 小節(jié)“賦值”):頁程序toString 方法會(huì)在打印語句里自動(dòng)調(diào)用,而 PassHandles 直接從 Object 繼承, 沒有 toString 的重新定義。因此,這里會(huì)采用 toString 的 Object 版本,打印 出對(duì)象的類,接著是那個(gè)對(duì)象所在的位置(不是句柄,而是對(duì)象的實(shí)際存儲(chǔ)位置)。 輸出結(jié)果如下:p inside main(): PassHandles1653748h inside f() : PassHandles1653748可以看到,無論p還是h引用的都是同一個(gè)對(duì)象。這比復(fù)制一個(gè)新的PassHandles 對(duì)象有效
4、多了,使我們能將一個(gè)參數(shù)發(fā)給一個(gè)方法。但這樣做也帶來了另一個(gè)重 要的問題。別名問題“別名”意味著多個(gè)句柄都試圖指向同一個(gè)對(duì)象,就象前面的例子展示的那樣。 若有人向那個(gè)對(duì)象里寫入一點(diǎn)什么東西,就會(huì)產(chǎn)生別名問題。若其他句柄的所有 者不希望那個(gè)對(duì)象改變,恐怕就要失望了。這可用下面這個(gè)簡(jiǎn)單的例子說明:頁程序?qū)ο旅孢@行:Alias1 y = x; / Assign the handle它會(huì)新建一個(gè) Alias1 句柄,但不是把它分配給由 new 創(chuàng)建的一個(gè)新鮮對(duì)象,而 是分配給一個(gè)現(xiàn)有的句柄。所以句柄 x 的內(nèi)容即對(duì)象 x 指向的地址被分 配給y,所以無論x還是y都與相同的對(duì)象連接起來。這樣一來,一旦x
5、的i在 下述語句中增值:x.i+;y的i值也必然受到影響。從最終的輸出就可以看出:頁上程序此時(shí)最直接的一個(gè)解決辦法就是干脆不這樣做:不要有意將多個(gè)句柄指向同一個(gè) 作用域內(nèi)的同一個(gè)對(duì)象。這樣做可使代碼更易理解和調(diào)試。然而,一旦準(zhǔn)備將句 柄作為一個(gè)自變量或參數(shù)傳遞這是 Java 設(shè)想的正常方法別名問題就會(huì) 自動(dòng)出現(xiàn),因?yàn)閯?chuàng)建的本地句柄可能修改“外部對(duì)象”(在方法作用域之外創(chuàng)建 的對(duì)象)。下面是一個(gè)例子:544 頁程序輸出如下:x: 7Calling f(x)x: 8方法改變了自己的參數(shù)外部對(duì)象。一旦遇到這種情況,必須判斷它是否合理, 用戶是否愿意這樣,以及是不是會(huì)造成問題。通常,我們調(diào)用一個(gè)方法是
6、為了產(chǎn)生返回值,或者用它改變?yōu)槠湔{(diào)用方法的那個(gè) 對(duì)象的狀態(tài)(方法其實(shí)就是我們向那個(gè)對(duì)象“發(fā)一條消息”的方式)。很少需要 調(diào)用一個(gè)方法來處理它的參數(shù);這叫作利用方法的“副作用”(SideEffec t)。 所以倘若創(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è)問 題展開的。12.2 制作本地副本稍微總結(jié)一下: Java 中的所有自變量或參數(shù)傳
7、遞都是通過傳遞句柄進(jìn)行的。也 就是說,當(dāng)我們傳遞“一個(gè)對(duì)象”時(shí),實(shí)際傳遞的只是指向位于方法外部的那個(gè) 對(duì)象的“一個(gè)句柄”。所以一旦要對(duì)那個(gè)句柄進(jìn)行任何修改,便相當(dāng)于修改外部 對(duì)象。此外:參數(shù)傳遞過程中會(huì)自動(dòng)產(chǎn)生別名問題 不存在本地對(duì)象,只有本地句柄句柄有自己的作用域,而對(duì)象沒有對(duì)象的“存在時(shí)間”在 Java 里不是個(gè)問題 沒有語言上的支持(如常量)可防止對(duì)象被修改(以避免別名的副作用)若只是從對(duì)象中讀取信息,而不修改它,傳遞句柄便是自變量傳遞中最有效的一 種形式。這種做非常恰當(dāng);默認(rèn)的方法一般也是最有效的方法。然而,有時(shí)仍需 將對(duì)象當(dāng)作“本地的”對(duì)待,使我們作出的改變只影響一個(gè)本地副本,不會(huì)對(duì)
8、外 面的對(duì)象造成影響。許多程序設(shè)計(jì)語言都支持在方法內(nèi)自動(dòng)生成外部對(duì)象的一個(gè) 本地副本(注釋)。盡管Java不具備這種能力,但允許我們達(dá)到同樣的效果。:在C語言中,通??刂频氖巧倭繑?shù)據(jù)位,默認(rèn)操作是按值傳遞。C+也必須 遵照這一形式,但按值傳遞對(duì)象并非肯定是一種有效的方式。此外,在C+中用 于支持按值傳遞的代碼也較難編寫,是件讓人頭痛的事情。按值傳遞 首先要解決術(shù)語的問題,最適合“按值傳遞”的看起來是自變量?!鞍粗祩鬟f” 以及它的含義取決于如何理解程序的運(yùn)行方式。最常見的意思是獲得要傳遞的任 何東西的一個(gè)本地副本,但這里真正的問題是如何看待自己準(zhǔn)備傳遞的東西。對(duì) 于“按值傳遞”的含義,目前存在兩
9、種存在明顯區(qū)別的見解: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è)句柄。也就是說,它允許我們將句柄假想成“對(duì)象”,因?yàn)樵?發(fā)出方法調(diào)用時(shí),系統(tǒng)會(huì)自動(dòng)照管兩者間的差異。Java 主要按值傳遞(無自變量),但對(duì)象卻是按引用傳遞的。得到這個(gè)結(jié) 論的前提是句柄只是對(duì)象的一個(gè)“別名”,所以不考慮傳遞句柄的問題,而是直 接指出“我準(zhǔn)備傳遞對(duì)象”
10、。由于將其傳遞進(jìn)入一個(gè)方法時(shí)沒有獲得對(duì)象的一個(gè) 本地副本,所以對(duì)象顯然不是按值傳遞的。 Sun 公司似乎在某種程度上支持這一 見解,因?yàn)樗氨A舻磳?shí)現(xiàn)”的關(guān)鍵字之一便是byvalue (按值)。但沒人知 道那個(gè)關(guān)鍵字什么時(shí)候可以發(fā)揮作用。盡管存在兩種不同的見解,但其間的分歧歸根到底是由于對(duì)“句柄”的不同解釋 造成的。我打算在本書剩下的部分里回避這個(gè)問題。大家不久就會(huì)知道,這個(gè)問 題爭(zhēng)論下去其實(shí)是沒有意義的最重要的是理解一個(gè)句柄的傳遞會(huì)使調(diào)用者 的對(duì)象發(fā)生意外的改變??寺?duì)象若需修改一個(gè)對(duì)象,同時(shí)不想改變調(diào)用者的對(duì)象,就要制作該對(duì)象的一個(gè)本地副 本。這也是本地副本最常見的一種用途。若決定制作一
11、個(gè)本地副本,只需簡(jiǎn)單地 使用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è)例子 指出Vector的clone()方法不能自動(dòng)嘗試克隆Vector內(nèi)包含的每個(gè)對(duì)象 由 于別名問題,老的Vector和克隆的Vector都包含了相同的
12、對(duì)象。我們通常把這 種情況叫作“簡(jiǎn)單復(fù)制”或者“淺層復(fù)制”,因?yàn)樗粡?fù)制了一個(gè)對(duì)象的“表 面”部分。實(shí)際對(duì)象除包含這個(gè)“表面”以外,還包括句柄指向的所有對(duì)象,以 及那些對(duì)象又指向的其他所有對(duì)象,由此類推。這便是“對(duì)象網(wǎng)”或“對(duì)象關(guān)系 網(wǎng)”的由來。若能復(fù)制下所有這張網(wǎng),便叫作“全面復(fù)制”或者“深層復(fù)制”。在輸出中可看到淺層復(fù)制的結(jié)果,注意對(duì)v2采取的行動(dòng)也會(huì)影響到v:548 頁上程序一般來說,由于不敢保證Vector里包含的對(duì)象是“可以克隆”(注釋)的, 所以最好不要試圖克隆那些對(duì)象。:“可以克隆”用英語講是cloneable,請(qǐng)留意Java庫中專門保留了這樣的 一個(gè)關(guān)鍵字。使類具有克隆能力 盡
13、管克隆方法是在所有類最基本的 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è)為pro tec ted)。這樣造成的后果就是:對(duì)那些簡(jiǎn)單地使 用一下這個(gè)類的客戶程序員來說,他們不會(huì)默認(rèn)地?fù)碛羞@個(gè)方法;其次,我們不 能利用指向基礎(chǔ)類的一個(gè)句柄來調(diào)用clone()(盡
14、管那樣做在某些情況下特別有 用,比如用多形性的方式克隆一系列對(duì)象)。在編譯期的時(shí)候,這實(shí)際是通知我 們對(duì)象不可克隆的一種方式而且最奇怪的是,Java庫中的大多數(shù)類都不能克隆。因此,假如我們執(zhí)行下述代碼:Integer x = new Integer(l);x = x.clone();那么在編譯期,就有一條討厭的錯(cuò)誤消息彈出,告訴我們不可訪問clone() 因?yàn)?Integer 并沒有覆蓋它,而且它對(duì) protected 版本來說是默認(rèn)的)。但是,假若我們是在一個(gè)從Object衍生出來的類中(所有類都是從Object衍生 的),就有權(quán)調(diào)用Objec t.clone(),因?yàn)樗恰?pro tec
15、 ted”,而且我們?cè)谝粋€(gè) 繼承器中。基礎(chǔ)類clone()提供了一個(gè)有用的功能一一它進(jìn)行的是對(duì)衍生類對(duì)象 的真正“按位”復(fù)制,所以相當(dāng)于標(biāo)準(zhǔn)的克隆行動(dòng)。然而,我們隨后需要將自己 的克隆操作設(shè)為public,否則無法訪問。總之,克隆時(shí)要注意的兩個(gè)關(guān)鍵問題 是:幾乎肯定要調(diào)用super.clone(),以及注意將克隆設(shè)為public。有時(shí)還想在更深層的衍生類中覆蓋clone(),否則就直接使用我們的clone()(現(xiàn) 在已成為public),而那并不一定是我們所希望的(然而,由于Objec t.clone() 已制作了實(shí)際對(duì)象的一個(gè)副本,所以也有可能允許這種情況)。protected的技 巧在這里
16、只能用一次:首次從一個(gè)不具備克隆能力的類繼承,而且想使一個(gè)類變 成“能夠克隆”。而在從我們的類繼承的任何場(chǎng)合,clone()方法都是可以使用 的,因?yàn)镴ava不可能在衍生之后反而縮小方法的訪問范圍。換言之,一旦對(duì)象 變得可以克隆,從它衍生的任何東西都是能夠克隆的,除非使用特殊的機(jī)制(后 面討論)令其“關(guān)閉”克隆能力。2.實(shí)現(xiàn) Cloneable 接口為使一個(gè)對(duì)象的克隆能力功成圓滿,還需要做另一件事情:實(shí)現(xiàn)Cloneable接口。 這個(gè)接口使人稍覺奇怪,因?yàn)樗强盏?!interface Cloneable 之所以要實(shí)現(xiàn)這個(gè)空接口,顯然不是因?yàn)槲覀儨?zhǔn)備上溯造型成一個(gè)Cloneable, 以及調(diào)用它
17、的某個(gè)方法。有些人認(rèn)為在這里使用接口屬于一種“欺騙”行為,因 為它使用的特性打的是別的主意,而非原來的意思。Cloneable in terface的實(shí) 現(xiàn)扮演了一個(gè)標(biāo)記的角色,封裝到類的類型中。兩方面的原因促成了 Cloneable interface的存在。首先,可能有一個(gè)上溯造型 句柄指向一個(gè)基礎(chǔ)類型,而且不知道它是否真的能克隆那個(gè)對(duì)象。在這種情況下, 可用instanceof關(guān)鍵字(第11章有介紹)調(diào)查句柄是否確實(shí)同一個(gè)能克隆的對(duì) 象連接:if(myHandle instanceof Cloneable) / .第二個(gè)原因是考慮到我們可能不愿所有對(duì)象類型都能克隆。所以O(shè)bject.cl
18、oneO 會(huì)驗(yàn)證一個(gè)類是否真的是實(shí)現(xiàn)了 Cloneable接口。若答案是否定的,則“擲”出 一個(gè)CloneNotSupportedException違例。所以在一般情況下,我們必須將 “implement Cloneable 作為對(duì)克隆能力提供支持的一部分。12.2.4成功的克隆理解了實(shí)現(xiàn)clone()方法背后的所有細(xì)節(jié)后,便可創(chuàng)建出能方便復(fù)制的類,以便 提供了一個(gè)本地副本:550-551頁程序不管怎樣,clone()必須能夠訪問,所以必須將其設(shè)為public (公共的)。其次, 作為clone ()的初期行動(dòng),應(yīng)調(diào)用clone ()的基礎(chǔ)類版本。這里調(diào)用的clone() 是Object內(nèi)部預(yù)
19、先定義好的。之所以能調(diào)用它,是由于它具有pro tec ted (受 到保護(hù)的)屬性,所以能在衍生的類里訪問。Object.clone()會(huì)檢查原先的對(duì)象有多大,再為新對(duì)象騰出足夠多的內(nèi)存,將所 有二進(jìn)制位從原來的對(duì)象復(fù)制到新對(duì)象。這叫作“按位復(fù)制”,而且按一般的想 法,這個(gè)工作應(yīng)該是由clone()方法來做的。但在Object.clone ()正式開始操作 前,首先會(huì)檢查一個(gè)類是否Cloneable,即是否具有克隆能力換言之,它是 否實(shí)現(xiàn)了 Cloneable接口。若未實(shí)現(xiàn),Objec t.clone ()就擲出一個(gè) CloneNotSupportedException違例,指出我們不能克隆
20、它。因此,我們最好用 一個(gè)try-catch塊將對(duì)super.clone ()的調(diào)用代碼包圍(或圭寸裝)起來,試圖捕 獲一個(gè)應(yīng)當(dāng)永不出現(xiàn)的違例(因?yàn)檫@里確實(shí)已實(shí)現(xiàn)了 Cloneable接口)。 在LocalCopy中,兩個(gè)方法g()和f()揭示出兩種參數(shù)傳遞方法間的差異。其中, g()演示的是按引用傳遞,它會(huì)修改外部對(duì)象,并返回對(duì)那個(gè)外部對(duì)象的一個(gè)引 用。而f()是對(duì)自變量進(jìn)行克隆,所以將其分離出來,并讓原來的對(duì)象保持獨(dú)立。 隨后,它繼續(xù)做它希望的事情。甚至能返回指向這個(gè)新對(duì)象的一個(gè)句柄,而且不 會(huì)對(duì)原來的對(duì)象產(chǎn)生任何副作用。注意下面這個(gè)多少有些古怪的語句:v = (MyObject)v.cl
21、one();它的作用正是創(chuàng)建一個(gè)本地副本。為避免被這樣的一個(gè)語句搞混淆,記住這種相 當(dāng)奇怪的編碼形式在 Java 中是完全允許的,因?yàn)橛幸粋€(gè)名字的所有東西實(shí)際都 是一個(gè)句柄。所以句柄 v 用于克隆一個(gè)它所指向的副本,而且最終返回指向基礎(chǔ) 類型Object的一個(gè)句柄(因?yàn)樗贠bject.clone ()中是那樣被定義的),隨后 必須將其造型為正確的類型。在 main() 中,兩種不同參數(shù)傳遞方式的區(qū)別在于它們分別測(cè)試了一個(gè)不同的方 法。輸出結(jié)果如下:552 頁程序大家要記住這樣一個(gè)事實(shí):Java對(duì)“是否等價(jià)”的測(cè)試并不對(duì)所比較對(duì)象的內(nèi) 部進(jìn)行檢查,從而核實(shí)它們的值是否相同。=和!=運(yùn)算符只是簡(jiǎn)
22、單地對(duì)比句柄的 內(nèi)容。若句柄內(nèi)的地址相同,就認(rèn)為句柄指向同樣的對(duì)象,所以認(rèn)為它們是“等 價(jià)”的。所以運(yùn)算符真正檢測(cè)的是“由于別名問題,句柄是否指向同一個(gè)對(duì) 象?”O(jiān)bjec t.clone ()的效果調(diào)用Objec t.clone ()時(shí),實(shí)際發(fā)生的是什么事情呢?當(dāng)我們?cè)谧约旱念惱锔采w clone()時(shí),什么東西對(duì)于super.clone ()來說是最關(guān)鍵的呢?根類中的clone() 方法負(fù)責(zé)建立正確的存儲(chǔ)容量,并通過“按位復(fù)制”將二進(jìn)制位從原始對(duì)象中復(fù) 制到新對(duì)象的存儲(chǔ)空間。也就是說,它并不只是預(yù)留存儲(chǔ)空間以及復(fù)制一個(gè)對(duì)象 實(shí)際需要調(diào)查出欲復(fù)制之對(duì)象的準(zhǔn)確大小,然后復(fù)制那個(gè)對(duì)象。由于所有這
23、些工作都是在由根類定義之clone()方法的內(nèi)部代碼中進(jìn)行的(根類并不知道要 從自己這里繼承出去什么),所以大家或許已經(jīng)猜到,這個(gè)過程需要用 RTTI 判 斷欲克隆的對(duì)象的實(shí)際大小。采取這種方式,clone()方法便可建立起正確數(shù)量 的存儲(chǔ)空間,并對(duì)那個(gè)類型進(jìn)行正確的按位復(fù)制。不管我們要做什么,克隆過程的第一個(gè)部分通常都應(yīng)該是調(diào)用 super.clone()。 通過進(jìn)行一次準(zhǔn)確的復(fù)制,這樣做可為后續(xù)的克隆進(jìn)程建立起一個(gè)良好的基礎(chǔ)。 隨后,可采取另一些必要的操作,以完成最終的克隆。為確切了解其他操作是什么,首先要正確理解Objec t.clone ()為我們帶來了什 么。特別地,它會(huì)自動(dòng)克隆所有
24、句柄指向的目標(biāo)嗎?下面這個(gè)例子可完成這種形 式的檢測(cè):553-554 頁程序一條Snake (蛇)由數(shù)段構(gòu)成,每一段的類型都是Snake。所以,這是一個(gè)一段 段鏈接起來的列表。所有段都是以循環(huán)方式創(chuàng)建的,每做好一段,都會(huì)使第一個(gè) 構(gòu)建器參數(shù)的值遞減,直至最終為零。而為給每段賦予一個(gè)獨(dú)一無二的標(biāo)記,第 二個(gè)參數(shù)(一個(gè)Char)的值在每次循環(huán)構(gòu)建器調(diào)用時(shí)都會(huì)遞增。increment() 方法的作用是循環(huán)遞增每個(gè)標(biāo)記,使我們能看到發(fā)生的變化;而 toString 則循環(huán)打印出每個(gè)標(biāo)記。輸出如下:554 頁中程序 這意味著只有第一段才是由Object.clone ()復(fù)制的,所以此時(shí)進(jìn)行的是一種 “淺
25、層復(fù)制”。若希望復(fù)制整條蛇即進(jìn)行“深層復(fù)制”必須在被覆蓋的 clone()里采取附加的操作。通常可在從一個(gè)能克隆的類里調(diào)用super.cloneO,以確保所有基礎(chǔ)類行動(dòng)(包 括Object.clone()能夠進(jìn)行。隨著是為對(duì)象內(nèi)每個(gè)句柄都明確調(diào)用一個(gè) clone();否則那些句柄會(huì)別名變成原始對(duì)象的句柄。構(gòu)建器的調(diào)用也大致相同 首先構(gòu)造基礎(chǔ)類,然后是下一個(gè)衍生的構(gòu)建器以此類推,直到位于最深 層的衍生構(gòu)建器。區(qū)別在于clone()并不是個(gè)構(gòu)建器,所以沒有辦法實(shí)現(xiàn)自動(dòng)克 隆。為了克隆,必須由自己明確進(jìn)行??寺『铣蓪?duì)象試圖深層復(fù)制合成對(duì)象時(shí)會(huì)遇到一個(gè)問題。必須假定成員對(duì)象中的clone()方法 也能
26、依次對(duì)自己的句柄進(jìn)行深層復(fù)制,以此類推。這使我們的操作變得復(fù)雜。為 了能正常實(shí)現(xiàn)深層復(fù)制,必須對(duì)所有類中的代碼進(jìn)行控制,或者至少全面掌握深 層復(fù)制中需要涉及的類,確保它們自己的深層復(fù)制能正確進(jìn)行。下面這個(gè)例子總結(jié)了面對(duì)一個(gè)合成對(duì)象進(jìn)行深層復(fù)制時(shí)需要做哪些事情:555-556 頁程序DepthReading和TemperatureReading非常相似;它們都只包含了基本數(shù)據(jù)類型。 所以clone()方法能夠非常簡(jiǎn)單:調(diào)用super.clone()并返回結(jié)果即可。注意兩 個(gè)類使用的clone()代碼是完全一致的。OceanReading 是由 DepthReading 和 Temperature
27、Reading 對(duì)象合并而成的。為 了對(duì)其進(jìn)行深層復(fù)制,clone()必須同時(shí)克隆OceanReading內(nèi)的句柄。為達(dá)到這 個(gè)目標(biāo),super.clone ()的結(jié)果必須造型成一個(gè)OceanReading對(duì)象(以便訪問 depth 和 temperature 句柄)。用 Vector 進(jìn)行深層復(fù)制 下面讓我們復(fù)習(xí)一下本章早些時(shí)候提出的Vector例子。這一次Int2類是可以克 隆的,所以能對(duì)Vector進(jìn)行深層復(fù)制:557-558 頁程序Int3自Int2繼承而來,并添加了一個(gè)新的基本類型成員int j。大家也許認(rèn)為 自己需要再次覆蓋clone(),以確保j得到復(fù)制,但實(shí)情并非如此。將Int
28、2的 clone()當(dāng)作Int3的clone ()調(diào)用時(shí),它會(huì)調(diào)用Object.clone(),判斷出當(dāng)前操 作的是Int3,并復(fù)制Int3內(nèi)的所有二進(jìn)制位。只要沒有新增需要克隆的句柄, 對(duì)Object.clone()的一個(gè)調(diào)用就能完成所有必要的復(fù)制無論clone()是在層次結(jié)構(gòu)多深的一級(jí)定義的。至此,大家可以總結(jié)出對(duì)Vector進(jìn)行深層復(fù)制的先決條件:在克隆了 Vector 后,必須在其中遍歷,并克隆由Vec tor指向的每個(gè)對(duì)象。為了對(duì)Hash table (散 列表)進(jìn)行深層復(fù)制,也必須采取類似的處理。這個(gè)例子剩余的部分顯示出克隆已實(shí)際進(jìn)行證據(jù)就是在克隆了對(duì)象以后,可 以自由改變它,而原
29、來那個(gè)對(duì)象不受任何影響。通過序列化進(jìn)行深層復(fù)制若研究一下第 10章介紹的那個(gè) Java 1.1 對(duì)象序列化示例,可能發(fā)現(xiàn)若在一個(gè)對(duì) 象序列化以后再撤消對(duì)它的序列化,或者說進(jìn)行裝配,那么實(shí)際經(jīng)歷的正是一個(gè) “克隆”的過程。那么為什么不用序列化進(jìn)行深層復(fù)制呢?下面這個(gè)例子通過計(jì)算執(zhí)行時(shí)間對(duì)比 了這兩種方法:559-560 頁程序其中,Thing2和Thing4包含了成員對(duì)象,所以需要進(jìn)行一些深層復(fù)制。一個(gè)有 趣的地方是盡管 Serializable 類很容易設(shè)置,但在復(fù)制它們時(shí)卻要做多得多的 工作。克隆涉及到大量的類設(shè)置工作,但實(shí)際的對(duì)象復(fù)制是相當(dāng)簡(jiǎn)單的。結(jié)果很 好地說明了一切。下面是幾次運(yùn)行分別
30、得到的結(jié)果:的確561 頁上程序除了序列化和克隆之間巨大的時(shí)間差異以外,我們也注意到序列化技術(shù)的運(yùn)行結(jié) 果并不穩(wěn)定,而克隆每一次花費(fèi)的時(shí)間都是相同的。使克隆具有更大的深度若新建一個(gè)類,它的基礎(chǔ)類會(huì)默認(rèn)為Object,并默認(rèn)為不具備克隆能力(就象 在下一節(jié)會(huì)看到的那樣)。只要不明確地添加克隆能力,這種能力便不會(huì)自動(dòng)產(chǎn) 生。但我們可以在任何層添加它,然后便可從那個(gè)層開始向下具有克隆能力。如 下所示:561-562 頁程序添加克隆能力之前,編譯器會(huì)阻止我們的克隆嘗試。一旦在Scientist里添加了 克隆能力,那么Scientist以及它的所有“后裔”都可以克隆。為什么有這個(gè)奇怪的設(shè)計(jì) 之所以感覺這
31、個(gè)方案的奇特,因?yàn)樗聦?shí)上的確如此。也許大家會(huì)奇怪它為什么 要象這樣運(yùn)行,而該方案背后的真正含義是什么呢?后面講述的是一個(gè)未獲證實(shí) 的故事一一大概是由于圍繞Java的許多買賣使其成為一種設(shè)計(jì)優(yōu)良的語言 但確實(shí)要花許多口舌才能講清楚這背后發(fā)生的所有事情。最初,Java只是作為一種用于控制硬件的語言而設(shè)計(jì),與因特網(wǎng)并沒有絲毫聯(lián) 系。象這樣一類面向大眾的語言一樣,其意義在于程序員可以對(duì)任意一個(gè)對(duì)象進(jìn) 行克隆。這樣一來,clone ()就放置在根類Object里面,但因?yàn)樗且环N公用方 式,因而我們通常能夠?qū)θ我庖粋€(gè)對(duì)象進(jìn)行克隆??磥磉@是最靈活的方式了,畢 竟它不會(huì)帶來任何害處。正當(dāng)Java看起來象一
32、種終級(jí)因特網(wǎng)程序設(shè)計(jì)語言的時(shí)候,情況卻發(fā)生了變化。 突然地,人們提出了安全問題,而且理所當(dāng)然,這些問題與使用對(duì)象有關(guān),我們 不愿望任何人克隆自己的保密對(duì)象。所以我們最后看到的是為原來那個(gè)簡(jiǎn)單、直 觀的方案添加的大量補(bǔ)?。?clone()在Object里被設(shè)置成“protected”。必須 將其覆蓋,并使用“implement Cloneable”,同時(shí)解決違例的問題。只有在準(zhǔn)備調(diào)用Object的clone()方法時(shí),才沒有必要使用Cloneable接口, 因?yàn)槟莻€(gè)方法會(huì)在運(yùn)行期間得到檢查,以確保我們的類實(shí)現(xiàn)了 Cloneable。但為 了保持連貫性(而且由于Cloneable無論如何都是空的)
33、,最好還是由自己實(shí)現(xiàn) Cloneable。克隆的控制為消除克隆能力,大家也許認(rèn)為只需將clone()方法簡(jiǎn)單地設(shè)為private (私有) 即可,但這樣是行不通的,因?yàn)椴荒懿捎靡粋€(gè)基礎(chǔ)類方法,并使其在衍生類中更 “私有”。所以事情并沒有這么簡(jiǎn)單。此外,我們有必要控制一個(gè)對(duì)象是否能夠 克隆。對(duì)于我們?cè)O(shè)計(jì)的一個(gè)類,實(shí)際有許多種方案都是可以采取的:保持中立,不為克隆做任何事情。也就是說,盡管不可對(duì)我們的類克隆,但 從它繼承的一個(gè)類卻可根據(jù)實(shí)際情況決定克隆。只有Object.clone ()要對(duì)類中 的字段進(jìn)行某些合理的操作時(shí),才可以作這方面的決定。支持clone(),采用實(shí)現(xiàn)Cloneable (可
34、克隆)能力的標(biāo)準(zhǔn)操作,并覆蓋 clone()。在被覆蓋的clone()中,可調(diào)用super.clone(),并捕獲所有違例(這 樣可使clone ()不“擲”出任何違例)。有條件地支持克隆。若類容納了其他對(duì)象的句柄,而那些對(duì)象也許能夠克隆 (集合類便是這樣的一個(gè)例子),就可試著克隆擁有對(duì)方句柄的所有對(duì)象;如果它們“擲”出了違例,只需讓這些違例通過即可。舉個(gè)例子來說,假設(shè)有一個(gè)特 殊的Vector,它試圖克隆自己容納的所有對(duì)象。編寫這樣的一個(gè)Vector時(shí),并 不知道客戶程序員會(huì)把什么形式的對(duì)象置入這個(gè)Vector中,所以并不知道它們 是否真的能夠克隆。不實(shí)現(xiàn)Cloneable(),但是將clo
35、ne()覆蓋成protected,使任何字段都具 有正確的復(fù)制行為。這樣一來,從這個(gè)類繼承的所有東西都能覆蓋clone(),并 調(diào)用super.clone()來產(chǎn)生正確的復(fù)制行為。注意在我們實(shí)現(xiàn)方案里,可以而且 應(yīng)該調(diào)用super.clone() 即使那個(gè)方法本來預(yù)期的是一個(gè)Cloneable對(duì)象(否則會(huì)擲出一個(gè)違例),因?yàn)闆]有人會(huì)在我們這種類型的對(duì)象上直接調(diào)用它。 它只有通過一個(gè)衍生類調(diào)用;對(duì)那個(gè)衍生類來說,如果要保證它正常工作,需實(shí) 現(xiàn) Cloneable。不實(shí)現(xiàn)Cloneable來試著防止克隆,并覆蓋clone(),以產(chǎn)生一個(gè)違例。為 使這一設(shè)想順利實(shí)現(xiàn),只有令從它衍生出來的任何類都調(diào)用
36、重新定義后的 clone()里的 suepr.clone()。將類設(shè)為final,從而防止克隆。若clone()尚未被我們的任何一個(gè)上級(jí)類 覆蓋,這一設(shè)想便不會(huì)成功。若已被覆蓋,那么再一次覆蓋它,并“擲”出一個(gè) CloneNotSupportedException (克隆不支持)違例。為擔(dān)??寺”唤梗瑢㈩愒O(shè) 為 final 是唯一的辦法。除此以外,一旦涉及保密對(duì)象或者遇到想對(duì)創(chuàng)建的對(duì)象 數(shù)量進(jìn)行控制的其他情況,應(yīng)該將所有構(gòu)建器都設(shè)為private,并提供一個(gè)或更 多的特殊方法來創(chuàng)建對(duì)象。采用這種方式,這些方法就可以限制創(chuàng)建的對(duì)象數(shù)量 以及它們的創(chuàng)建條件一一一種特殊情況是第16章要介紹的sin
37、gle ton (獨(dú)子) 方案。下面這個(gè)例子總結(jié)了克隆的各種實(shí)現(xiàn)方法,然后在層次結(jié)構(gòu)中將其“關(guān)閉”:564-565 頁程序第一個(gè)類Ordinary代表著大家在本書各處最常見到的類:不支持克隆,但在它 正式應(yīng)用以后,卻也不禁止對(duì)其克隆。但假如有一個(gè)指向Ordinary對(duì)象的句柄, 而且那個(gè)對(duì)象可能是從一個(gè)更深的衍生類上溯造型來的,便不能判斷它到底能不 能克隆。WrongClone 類揭示了實(shí)現(xiàn)克隆的一種不正確途徑。它確實(shí)覆蓋了Object.clone(),并將那個(gè)方法設(shè)為public,但卻沒有實(shí)現(xiàn)Cloneable。所以一 旦發(fā)出對(duì)super.clone ()的調(diào)用(由于對(duì)Object.clon
38、e ()的一個(gè)調(diào)用造成的), 便會(huì)無情地?cái)S出CloneNotSupportedException違例。在IsCloneable中,大家看到的才是進(jìn)行克隆的各種正確行動(dòng):先覆蓋clone(), 并實(shí)現(xiàn)了 Cloneable。但是,這個(gè)clone()方法以及本例的另外幾個(gè)方法并不捕 獲CloneNotSupportedException違例,而是任由它通過,并傳遞給調(diào)用者。隨 后,調(diào)用者必須用一個(gè)try-catch代碼塊把它包圍起來。在我們自己的clone() 方法中,通常需要在clone ()內(nèi)部捕獲CloneNotSupportedException違例,而 不是任由它通過。正如大家以后會(huì)理解
39、的那樣,對(duì)這個(gè)例子來說,讓它通過是最 正確的做法。類NoMore試圖按照J(rèn)ava設(shè)計(jì)者打算的那樣“關(guān)閉”克隆:在衍生類clone()中, 我們擲出 CloneNotSupportedException 違例o TryMore 類中的 clone()方法正確 地調(diào)用super.clone(),并解析成NoMore.cloneO,后者擲出一個(gè)違例并禁止克 隆。但在已被覆蓋的clone()方法中,假若程序員不遵守調(diào)用super.clone ()的“正 確”方法,又會(huì)出現(xiàn)什么情況呢?在BackOn中,大家可看到實(shí)際會(huì)發(fā)生什么。 這個(gè)類用一個(gè)獨(dú)立的方法duplicate()制作當(dāng)前對(duì)象的一個(gè)副本,并在c
40、lone() 內(nèi)部調(diào)用這個(gè)方法,而不是調(diào)用super.clone。違例永遠(yuǎn)不會(huì)產(chǎn)生,而且新類 是可以克隆的。因此,我們不能依賴“擲”出一個(gè)違例的方法來防止產(chǎn)生一個(gè)可 克隆的類。唯一安全的方法在ReallyNoMore中得到了演示,它設(shè)為final,所 以不可繼承。這意味著假如clone()在final類中擲出了一個(gè)違例,便不能通過 繼承來進(jìn)行修改,并可有效地禁止克隆(不能從一個(gè)擁有任意繼承級(jí)數(shù)的類中明 確調(diào)用Object.clone();只能調(diào)用super.clone(),它只可訪問直接基礎(chǔ)類)。 因此,只要制作一些涉及安全問題的對(duì)象,就最好把那些類設(shè)為 final。在類CheckClonea
41、ble中,我們看到的第一個(gè)類是tryToClone(),它能接納任何 Ordinary 對(duì)象,并用 instanceof 檢查它是否能夠克隆。若答案是肯定的,就將 對(duì)象造型成為一個(gè)IsCloneable,調(diào)用clone(),并將結(jié)果造型回Ordinary,最 后捕獲有可能產(chǎn)生的任何違例。請(qǐng)注意用運(yùn)行期類型鑒定(見第11章)打印出 類名,使自己看到發(fā)生的一切情況。在main()中,我們創(chuàng)建了不同類型的Ordinary對(duì)象,并在數(shù)組定義中上溯造型 成為Ordinary。在這之后的頭兩行代碼創(chuàng)建了一個(gè)純粹的Ordinary對(duì)象,并試 圖對(duì)其克隆。然而,這些代碼不會(huì)得到編譯,因?yàn)閏lone ()是Ob
42、ject中的一個(gè) pro tec ted (受到保護(hù)的)方法。代碼剩余的部分將遍歷數(shù)組,并試著克隆每個(gè) 對(duì)象,分別報(bào)告它們的成功或失敗。輸出如下:567-568 頁程序 總之,如果希望一個(gè)類能夠克隆,那么:實(shí)現(xiàn) Cloneable 接口覆蓋 clone()在自己的 clone ()中調(diào)用 super.clone()在自己的clone ()中捕獲違例 這一系列步驟能達(dá)到最理想的效果。副本構(gòu)建器 克隆看起來要求進(jìn)行非常復(fù)雜的設(shè)置,似乎還該有另一種替代方案。一個(gè)辦法是 制作特殊的構(gòu)建器,令其負(fù)責(zé)復(fù)制一個(gè)對(duì)象。在C+中,這叫作“副本構(gòu)建器”。 剛開始的時(shí)候,這好象是一種非常顯然的解決方案(如果你是C+
43、程序員,這個(gè) 方法就更顯親切)。下面是一個(gè)實(shí)際的例子:568-571 頁程序這個(gè)例子第一眼看上去顯得有點(diǎn)奇怪。不同水果的質(zhì)量肯定有所區(qū)別,但為什么 只是把代表那些質(zhì)量的數(shù)據(jù)成員直接置入Fruit (水果)類?有兩方面可能的原 因。第一個(gè)是我們可能想簡(jiǎn)便地插入或修改質(zhì)量。注意Fruit有一個(gè)protected (受到保護(hù)的)addQualities()方法,它允許衍生類來進(jìn)行這些插入或修改操作 (大家或許會(huì)認(rèn)為最合乎邏輯的做法是在Fruit中使用一個(gè)protected構(gòu)建器, 用它獲取 FruitQualities 參數(shù),但構(gòu)建器不能繼承,所以不可在第二級(jí)或級(jí)數(shù) 更深的類中使用它)。通過將水果的
44、質(zhì)量置入一個(gè)獨(dú)立的類,可以得到更大的靈 活性,其中包括可以在特定 Fruit 對(duì)象的存在期間中途更改質(zhì)量。之所以將 FruitQualities 設(shè)為一個(gè)獨(dú)立的對(duì)象,另一個(gè)原因是考慮到我們有時(shí) 希望添加新的質(zhì)量,或者通過繼承與多形性改變行為。注意對(duì) GreenZebra 來說 (這實(shí)際是西紅柿的一類我已栽種成功,它們簡(jiǎn)直令人難以置信),構(gòu)建器 會(huì)調(diào)用addQualities(),并為其傳遞一個(gè)ZebraQualities對(duì)象。該對(duì)象是從 FruitQualities 衍生出來的,所以能與基礎(chǔ)類中的 FruitQualities 句柄聯(lián)系在 一起。當(dāng)然,一旦GreenZebra使用FruitQu
45、alities,就必須將其下溯造型成為 正確的類型(就象evaluate()中展示的那樣),但它肯定知道類型是 ZebraQualities。大家也看到有一個(gè)Seed (種子)類,F(xiàn)ruit (大家都知道,水果含有自己的種子) 包含了一個(gè) Seed 數(shù)組。最后,注意每個(gè)類都有一個(gè)副本構(gòu)建器,而且每個(gè)副本構(gòu)建器都必須關(guān)心為基礎(chǔ) 類和成員對(duì)象調(diào)用副本構(gòu)建器的問題,從而獲得“深層復(fù)制”的效果。對(duì)副本構(gòu) 建器的測(cè)試是在CopyCons true tor類內(nèi)進(jìn)行的。方法ripen()需要獲取一個(gè) Tomato 參數(shù),并對(duì)其執(zhí)行副本構(gòu)建工作,以便復(fù)制對(duì)象: t = new Tomato(t);而slice
46、 ()需要獲取一個(gè)更常規(guī)的Fruit對(duì)象,而且對(duì)它進(jìn)行復(fù)制:f = new Fruit(f);它們都在main()中伴隨不同種類的Fruit進(jìn)行測(cè)試。下面是輸出結(jié)果:572 頁上程序 從中可以看出一個(gè)問題。在slice()內(nèi)部對(duì)Toma to進(jìn)行了副本構(gòu)建工作以后, 結(jié)果便不再是一個(gè)Toma to對(duì)象,而只是一個(gè)Frui t。它已丟失了作為一個(gè)Toma to (西紅柿)的所有特征。此外,如果采用一個(gè)GreenZebra, ripen()和slice() 會(huì)把它分別轉(zhuǎn)換成一個(gè)Toma to和一個(gè)Frui t。所以非常不幸,假如想制作對(duì)象 的一個(gè)本地副本,Java中的副本構(gòu)建器便不是特別適合我們。
47、1.為什么在C+的作用比在Java中大?副本構(gòu)建器是C+的一個(gè)基本構(gòu)成部分,因?yàn)樗茏詣?dòng)產(chǎn)生對(duì)象的一個(gè)本地副 本。但前面的例子確實(shí)證明了它不適合在Java中使用,為什么呢?在Java中, 我們操控的一切東西都是句柄,而在C+中,卻可以使用類似于句柄的東西,也 能直接傳遞對(duì)象。這時(shí)便要用到C+的副本構(gòu)建器:只要想獲得一個(gè)對(duì)象,并按 值傳遞它,就可以復(fù)制對(duì)象。所以它在C+里能很好地工作,但應(yīng)注意這套機(jī)制 在 Java 里是很不通的,所以不要用它。只讀類盡管在一些特定的場(chǎng)合,由clone()產(chǎn)生的本地副本能夠獲得我們希望的結(jié)果, 但程序員(方法的作者)不得不親自禁止別名處理的副作用。假如想制作一個(gè)庫
48、, 令其具有常規(guī)用途,但卻不能擔(dān)保它肯定能在正確的類中得以克隆,這時(shí)又該怎 么辦呢?更有可能的一種情況是,假如我們想讓別名發(fā)揮積極的作用禁止不 必要的對(duì)象復(fù)制但卻不希望看到由此造成的副作用,那么又該如何處理呢? 一個(gè)辦法是創(chuàng)建“不變對(duì)象”,令其從屬于只讀類??啥x一個(gè)特殊的類,使其 中沒有任何方法能造成對(duì)象內(nèi)部狀態(tài)的改變。在這樣的一個(gè)類中,別名處理是沒 有問題的。因?yàn)槲覀冎荒茏x取內(nèi)部狀態(tài),所以當(dāng)多處代碼都讀取相同的對(duì)象時(shí), 不會(huì)出現(xiàn)任何副作用。作為“不變對(duì)象”一個(gè)簡(jiǎn)單例子,Java的標(biāo)準(zhǔn)庫包含了 “圭寸裝器”(wrapper) 類,可用于所有基本數(shù)據(jù)類型。大家可能已發(fā)現(xiàn)了這一點(diǎn),如果想在一個(gè)象
49、 Vector (只采用Object句柄)這樣的集合里保存一個(gè)int數(shù)值,可以將這個(gè)int 封裝到標(biāo)準(zhǔn)庫的Integer類內(nèi)部。如下所示:573 頁中程序Integer類(以及基本的“封裝器”類)用簡(jiǎn)單的形式實(shí)現(xiàn)了“不變性”:它們 沒有提供可以修改對(duì)象的方法。若確實(shí)需要一個(gè)容納了基本數(shù)據(jù)類型的對(duì)象,并想對(duì)基本數(shù)據(jù)類型進(jìn)行修改,就 必須親自創(chuàng)建它們。幸運(yùn)的是,操作非常簡(jiǎn)單:574 頁程序注意 n 在這里簡(jiǎn)化了我們的編碼。 若默認(rèn)的初始化為零已經(jīng)足夠(便不需要構(gòu)建器),而且不用考慮把它打印出來(便不需要toString),那么IntValue甚至還能更加簡(jiǎn)單。如下所示:class IntValue
50、 int n; 將元素取出來,再對(duì)其進(jìn)行造型,這多少顯得有些笨拙,但那是Vector的問題, 不是IntValue的錯(cuò)。創(chuàng)建只讀類 完全可以創(chuàng)建自己的只讀類,下面是個(gè)簡(jiǎn)單的例子:575頁程序所有數(shù)據(jù)都設(shè)為private,可以看到?jīng)]有任何public方法對(duì)數(shù)據(jù)作出修改。事 實(shí)上,確實(shí)需要修改一個(gè)對(duì)象的方法是quadruple。,但它的作用是新建一個(gè) Immutablel對(duì)象,初始對(duì)象則是原圭寸未動(dòng)的。方法f()需要取得一個(gè)Immutablel對(duì)象,并對(duì)其采取不同的操作,而main()的 輸出顯示出沒有對(duì)x作任何修改。因此,x對(duì)象可別名處理許多次,不會(huì)造成任 何傷害,因?yàn)楦鶕?jù)Immutable1類
51、的設(shè)計(jì),它能保證對(duì)象不被改動(dòng)。“一成不變”的弊端 從表面看,不變類的建立似乎是一個(gè)好方案。但是,一旦真的需要那種新類型的 一個(gè)修改的對(duì)象,就必須辛苦地進(jìn)行新對(duì)象的創(chuàng)建工作,同時(shí)還有可能涉及更頻 繁的垃圾收集。對(duì)有些類來說,這個(gè)問題并不是很大。但對(duì)其他類來說(比如 St ring類),這一方案的代價(jià)顯得太高了。為解決這個(gè)問題,我們可以創(chuàng)建一個(gè)“同志”類,并使其能夠修改。以后只要涉 及大量的修改工作,就可換為使用能修改的同志類。完事以后,再切換回不可變 的類。因此,上例可改成下面這個(gè)樣子:577頁程序和往常一樣,Immutable2包含的方法保留了對(duì)象不可變的特征,只要涉及修改, 就創(chuàng)建新的對(duì)象。
52、完成這些操作的是add()和multiply ()方法。同志類叫作 Mut able,它也含有add()和mul tiply ()方法。但這些方法能夠修改Mu table對(duì) 象,而不是新建一個(gè)。除此以外,Mutable的一個(gè)方法可用它的數(shù)據(jù)產(chǎn)生一個(gè) Immutable2對(duì)象,反之亦然。兩個(gè)靜態(tài)方法modify1()和modify2()揭示出獲得同樣結(jié)果的兩種不同方法。在 modify1()中,所有工作都是在Immutable2類中完成的,我們可看到在進(jìn)程中創(chuàng) 建了四個(gè)新的Immutable2對(duì)象(而且每次重新分配了 val,前一個(gè)對(duì)象就成為 垃圾)。在方法modify2 ()中,可看到它的第一
53、個(gè)行動(dòng)是獲取Immutable2 y,然后從中生 成一個(gè)Mutable (類似于前面對(duì)clone ()的調(diào)用,但這一次創(chuàng)建了一個(gè)不同類型 的對(duì)象)。隨后,用Mu table對(duì)象進(jìn)行大量修改操作,同時(shí)用不著新建許多對(duì)象。 最后,它切換回Immut able2。在這里,我們只創(chuàng)建了兩個(gè)新對(duì)象(Mu table和 Immutable2的結(jié)果),而不是四個(gè)。這一方法特別適合在下述場(chǎng)合應(yīng)用:需要不可變的對(duì)象,而且經(jīng)常需要進(jìn)行大量修改,或者創(chuàng)建新的不變對(duì)象代價(jià)太高不變字串請(qǐng)觀察下述代碼:577-578 頁程序q 傳遞進(jìn)入 upcase() 時(shí),它實(shí)際是 q 的句柄的一個(gè)副本。該句柄連接的對(duì)象實(shí)際 只在一個(gè)
54、統(tǒng)一的物理位置處。句柄四處傳遞的時(shí)候,它的句柄會(huì)得到復(fù)制。若觀察對(duì)upcase ()的定義,會(huì)發(fā)現(xiàn)傳遞進(jìn)入的句柄有一個(gè)名字s,而且該名字只 有在upcase ()執(zhí)行期間才會(huì)存在。upcase ()完成后,本地句柄s便會(huì)消失,而 upcase()返回結(jié)果一一還是原來那個(gè)字串,只是所有字符都變成了大寫。當(dāng)然, 它返回的實(shí)際是結(jié)果的一個(gè)句柄。但它返回的句柄最終是為一個(gè)新對(duì)象的,同時(shí) 原來的 q 并未發(fā)生變化。所有這些是如何發(fā)生的呢?1. 隱式常數(shù)若使用下述語句:String s = asdf;String x = Stringer.upcase(s);那么真的希望upcase()方法改變自變量或者
55、參數(shù)嗎?我們通常是不愿意的,因 為作為提供給方法的一種信息,自變量一般是拿給代碼的讀者看的,而不是讓他 們修改。這是一個(gè)相當(dāng)重要的保證,因?yàn)樗勾a更易編寫和理解。為了在C+中實(shí)現(xiàn)這一保證,需要一個(gè)特殊關(guān)鍵字的幫助:const。利用這個(gè)關(guān) 鍵字,程序員可以保證一個(gè)句柄(C+叫“指針”或者“引用”)不會(huì)被用來修 改原始的對(duì)象。但這樣一來,C+程序員需要用心記住在所有地方都使用const。 這顯然易使人混淆,也不容易記住。2. 覆蓋+和 StringBuffer 利用前面提到的技術(shù), String 類的對(duì)象被設(shè)計(jì)成“不可變”。若查閱聯(lián)機(jī)文檔 中關(guān)于St ring類的內(nèi)容(本章稍后還要總結(jié)它),就會(huì)
56、發(fā)現(xiàn)類中能夠修改St ring 的每個(gè)方法實(shí)際都創(chuàng)建和返回了一個(gè)嶄新的St ring對(duì)象,新對(duì)象里包含了修改 過的信息原來的St ring是原圭寸未動(dòng)的。因此,Java里沒有與C+的cons t對(duì)應(yīng)的特性可用來讓編譯器支持對(duì)象的不可變能力。若想獲得這一能力,可以自 行設(shè)置,就象St ring那樣。由于St ring對(duì)象是不可變的,所以能夠根據(jù)情況對(duì)一個(gè)特定的St ring進(jìn)行多次 別名處理。因?yàn)樗侵蛔x的,所以一個(gè)句柄不可能會(huì)改變一些會(huì)影響其他句柄的 東西。因此,只讀對(duì)象可以很好地解決別名問題。通過修改產(chǎn)生對(duì)象的一個(gè)嶄新版本,似乎可以解決修改對(duì)象時(shí)的所有問題,就象 St ring那樣。但對(duì)某些
57、操作來講,這種方法的效率并不高。一個(gè)典型的例子便 是為St ring對(duì)象覆蓋的運(yùn)算符“ + ”?!案采w”意味著在與一個(gè)特定的類使用 時(shí),它的含義已發(fā)生了變化(用于St ring的“ + ”和“+二”是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+中容易得多。針對(duì)St ring對(duì)象使用時(shí),
58、“ + ”允許我們將不同的字串連接起來:頁中程序可以想象出它“可能”是如何工作的:字串a(chǎn)bc可以有一個(gè)方法append(),它 新建了一個(gè)字串,其中包含abc以及foo的內(nèi)容;這個(gè)新字串然后再創(chuàng)建另一 個(gè)新字串,在其中添加def;以此類推。這一設(shè)想是行得通的,但它要求創(chuàng)建大量字串對(duì)象。盡管最終的目的只是獲得包 含了所有內(nèi)容的一個(gè)新字串,但中間卻要用到大量字串對(duì)象,而且要不斷地進(jìn)行 垃圾收集。我懷疑Java的設(shè)計(jì)者是否先試過種方法(這是軟件開發(fā)的一個(gè)教訓(xùn) 除非自己試試代碼,并讓某些東西運(yùn)行起來,否則不可能真正了解系統(tǒng))。 我還懷疑他們是否早就發(fā)現(xiàn)這樣做獲得的性能是不能接受的。解決的方法是象前面介
59、紹的那樣制作一個(gè)可變的同志類。對(duì)字串來說,這個(gè)同志 類叫作StringBuffer,編譯器可以自動(dòng)創(chuàng)建一個(gè)StringBuffer,以便計(jì)算特定 的表達(dá)式,特別是面向St ring對(duì)象應(yīng)用覆蓋過的運(yùn)算符+和+二時(shí)。下面這個(gè)例子 可以解決這個(gè)問題:頁程序創(chuàng)建字串s時(shí),編譯器做的工作大致等價(jià)于后面使用sb的代碼創(chuàng)建一個(gè) StringBuffer,并用append ()將新字符直接加入StringBuffer對(duì)象(而不是每 次都產(chǎn)生新對(duì)象)。盡管這樣做更有效,但不值得每次都創(chuàng)建象abc 和def 這樣的引號(hào)字串,編譯器會(huì)把它們都轉(zhuǎn)換成St ring對(duì)象。所以盡管St ringBuffer 提供了更高
60、的效率,但會(huì)產(chǎn)生比我們希望的多得多的對(duì)象。String 和 StringBuffer 類這里總結(jié)一下同時(shí)適用于St ring和St ringBuffer的方法,以便對(duì)它們相互間的 溝通方式有一個(gè)印象。這些表格并未把每個(gè)單獨(dú)的方法都包括進(jìn)去,而是包含了 與本次討論有重要關(guān)系的方法。那些已被覆蓋的方法用單獨(dú)一行總結(jié)。 首先總結(jié) String 類的各種方法: 方法 自變量,覆蓋 用途 構(gòu)建器 已被覆蓋:默認(rèn), String, StringBuffer, char 數(shù)組, byte 數(shù)組 創(chuàng)建 String 對(duì)象leng th()無St ring中的字符數(shù)量 charAt() int Index 位于
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(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ǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024版授權(quán)經(jīng)營合同3篇
- 專項(xiàng)資金貸款合同:2024版標(biāo)準(zhǔn)文本精簡(jiǎn)版版B版
- 2024褚貞與翻譯公司翻譯服務(wù)合同
- 2024甲乙雙方關(guān)于建筑工程電氣安裝項(xiàng)目合同
- 2025年度數(shù)據(jù)中心網(wǎng)絡(luò)布線與維護(hù)服務(wù)合同3篇
- 2025年度特色小吃店轉(zhuǎn)讓合同范本二零二五年度3篇
- 專業(yè)檢測(cè)服務(wù)第三方協(xié)議(2024版)版A版
- 二手家具買賣合同2024版版B版
- 2024聘用副總經(jīng)理工作職責(zé)與績(jī)效目標(biāo)合同3篇
- 二零二五年度企業(yè)并購項(xiàng)目補(bǔ)充協(xié)議范本3篇
- 《城市環(huán)境污染》課件
- 食材質(zhì)量控制方案
- 2024-2025學(xué)年外研版七年級(jí)英語下冊(cè) Unit1單詞背誦(不帶音標(biāo))
- 餐廳清潔與打掃服務(wù)合同范本
- 期末試題-2024-2025學(xué)年人教PEP版英語六年級(jí)上冊(cè) (含答案)
- 重癥??谱o(hù)士理論考試試題及答案
- 醫(yī)療器械經(jīng)營質(zhì)量體系文件-質(zhì)量管理制度
- 劉潤年度演講2024
- 考研計(jì)算機(jī)學(xué)科專業(yè)基礎(chǔ)(408)研究生考試試題與參考答案(2025年)
- 2024年浙江省普通高中學(xué)業(yè)水平適應(yīng)性考試歷史試題(解析版)
- 4《試種一粒籽》第二課時(shí)(教學(xué)設(shè)計(jì))2023-2024學(xué)年統(tǒng)編版道德與法治二年級(jí)下冊(cè)
評(píng)論
0/150
提交評(píng)論