![第12章_傳遞和返回對象解析_第1頁](http://file2.renrendoc.com/fileroot_temp3/2021-6/11/b6bc40b6-ccf0-4df1-98b0-f196cad28404/b6bc40b6-ccf0-4df1-98b0-f196cad284041.gif)
![第12章_傳遞和返回對象解析_第2頁](http://file2.renrendoc.com/fileroot_temp3/2021-6/11/b6bc40b6-ccf0-4df1-98b0-f196cad28404/b6bc40b6-ccf0-4df1-98b0-f196cad284042.gif)
![第12章_傳遞和返回對象解析_第3頁](http://file2.renrendoc.com/fileroot_temp3/2021-6/11/b6bc40b6-ccf0-4df1-98b0-f196cad28404/b6bc40b6-ccf0-4df1-98b0-f196cad284043.gif)
![第12章_傳遞和返回對象解析_第4頁](http://file2.renrendoc.com/fileroot_temp3/2021-6/11/b6bc40b6-ccf0-4df1-98b0-f196cad28404/b6bc40b6-ccf0-4df1-98b0-f196cad284044.gif)
![第12章_傳遞和返回對象解析_第5頁](http://file2.renrendoc.com/fileroot_temp3/2021-6/11/b6bc40b6-ccf0-4df1-98b0-f196cad28404/b6bc40b6-ccf0-4df1-98b0-f196cad284045.gif)
版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年武漢科技職業(yè)學(xué)院高職單招職業(yè)適應(yīng)性測試近5年??及鎱⒖碱}庫含答案解析
- 2025年榆林職業(yè)技術(shù)學(xué)院高職單招語文2018-2024歷年參考題庫頻考點(diǎn)含答案解析
- 課題申報(bào)參考:涉外民商事合同中經(jīng)濟(jì)制裁法適用問題研究
- 《動(dòng)物科學(xué)養(yǎng)殖技術(shù)》課件
- 液體化工產(chǎn)品購銷合同
- 公司員工聘用合同范年
- 跨境投資與并購項(xiàng)目合同
- 訂購水處理設(shè)備合同
- 全新茶葉銷售購銷合同下載
- 洗車店租賃合同
- 二零二五版電力設(shè)施維修保養(yǎng)合同協(xié)議3篇
- 最經(jīng)典凈水廠施工組織設(shè)計(jì)
- VDA6.3過程審核報(bào)告
- 2024-2030年中國并購基金行業(yè)發(fā)展前景預(yù)測及投資策略研究報(bào)告
- 2024年湖南商務(wù)職業(yè)技術(shù)學(xué)院單招職業(yè)適應(yīng)性測試題庫帶答案
- 骨科手術(shù)中常被忽略的操作課件
- 《湖南師范大學(xué)》課件
- 2024年全國各地中考試題分類匯編:作文題目
- 典范英語8-15Here comes trouble原文翻譯
- 六安市葉集化工園區(qū)污水處理廠及配套管網(wǎng)一期工程環(huán)境影響報(bào)告書
- 運(yùn)動(dòng)技能學(xué)習(xí)與控制課件第一章運(yùn)動(dòng)技能學(xué)習(xí)與控制概述
評(píng)論
0/150
提交評(píng)論