【正文】
。但這并不能保證以后的版本不會(huì)實(shí)現(xiàn),因?yàn)椤癵eneric”這個(gè)詞已被Java“保留到將來(lái)實(shí)現(xiàn)”(在Ada語(yǔ)言中,“generic”被用來(lái)實(shí)現(xiàn)它的模板)。在C++中,用于實(shí)現(xiàn)參數(shù)化類型的關(guān)鍵字是template(模板)。例如,通過(guò)使用一個(gè)參數(shù)化集合,編譯器可對(duì)那個(gè)集合進(jìn)行定制,使其只接受Shape,而且只提取Shape。既然如此,我們能不能創(chuàng)建一個(gè)“智能”集合,令其知道自己容納的類型呢?這樣做可消除下溯造型的必要以及潛在的錯(cuò)誤。但在從一個(gè)集合提取對(duì)象句柄時(shí),必須用某種方式準(zhǔn)確地記住它們是什么,以保證下溯造型的正確進(jìn)行。但這也不是絕對(duì)危險(xiǎn)的,因?yàn)榧偃缦滤菰煨统慑e(cuò)誤的東西,會(huì)得到我們稱為“違例”(Exception)的一種運(yùn)行期錯(cuò)誤。舉個(gè)例子來(lái)說(shuō),我們知道在上溯造型的時(shí)候,Circle(圓)屬于Shape(幾何形狀)的一種類型,所以上溯造型是安全的。而是下溯造型成一種更“特殊”的類型。所以怎樣才能歸還它的本來(lái)面貌,調(diào)用早先置入集合的那個(gè)對(duì)象的有用接口呢?在這里,我們?cè)俅斡玫搅嗽煨停–ast)。但由于集合只能容納Object,所以在我們向集合里添加對(duì)象句柄時(shí),它會(huì)上溯造型成Object,這樣便丟失了它的身份或者標(biāo)識(shí)信息。這使我們對(duì)它的重復(fù)使用變得非常簡(jiǎn)便。下溯造型與模板/通用性為了使這些集合能夠重復(fù)使用,或者“再生”,Java提供了一種通用類型,以前曾把它叫作“Object”。這樣一來(lái),我們就可以方便地取用各種集合,將其插入自己的程序。這對(duì)系統(tǒng)級(jí)的操作來(lái)說(shuō)顯得特別重要,比如違例控制;而且也能在程序設(shè)計(jì)時(shí)獲得更大的靈活性。如果沒(méi)有這種單根結(jié)構(gòu),而且系統(tǒng)通過(guò)一個(gè)句柄來(lái)操縱對(duì)象,那么實(shí)現(xiàn)垃圾收集器的途徑會(huì)有很大的不同,而且會(huì)面臨許多障礙。利用單根結(jié)構(gòu),我們可以更方便地實(shí)現(xiàn)一個(gè)垃圾收集器。在自己的系統(tǒng)中,我們知道對(duì)每個(gè)對(duì)象都能進(jìn)行一些基本操作。但假如你是一名新手,首次接觸這類設(shè)計(jì),象Java那樣的替換方案也許會(huì)更省事一些。理所當(dāng)然地,這也需要付出額外的精力使新接口與自己的設(shè)計(jì)方案配合(可能還需要多重繼承)。但假期我們想進(jìn)行純粹的面向?qū)ο缶幊蹋敲幢仨殬?gòu)建自己的結(jié)構(gòu),以期獲得與內(nèi)建到其他OOP語(yǔ)言里的同樣的便利。另一種方案(就象C++那樣)是我們不能保證所有東西都屬于相同的基本類型。這種“單根結(jié)構(gòu)”具有許多方面的優(yōu)點(diǎn)。2 單根結(jié)構(gòu)在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,由于C++的引入而顯得尤為突出的一個(gè)問(wèn)題是:所有類最終是否都應(yīng)從單獨(dú)一個(gè)基礎(chǔ)類繼承。我們可能只需要一種類型的序列。如果那個(gè)儲(chǔ)藏所能滿足我們的所有需要,就完全沒(méi)必要關(guān)心它具體是如何實(shí)現(xiàn)的(這是大多數(shù)類型對(duì)象的一個(gè)基本概念)。由于抽象是通過(guò)繼承器進(jìn)行的,所以能在兩者方便地切換,對(duì)代碼的影響則顯得微不足道。在設(shè)計(jì)階段,我們可以先從一個(gè)列表開(kāi)始。但在另一方面,如果想在序列中部插入一個(gè)元素,用列表就比用矢量劃算得多。但在一個(gè)鏈接列表中,若想到處移動(dòng),并隨機(jī)挑選一個(gè)元素,就需付出“慘重”的代價(jià)。但在執(zhí)行一些特定的任務(wù)時(shí),需要的開(kāi)銷卻是完全不同的。最好的例子便是矢量(Vector)和列表(List)的區(qū)別。利用這個(gè)特征,我們解決問(wèn)題時(shí)便有更大的靈活性。首先,集合提供了不同的接口類型以及外部行為。如果一種類型的序列即可滿足我們的所有要求,那么完全沒(méi)有必要再換用不同的類型。從設(shè)計(jì)角度出發(fā),我們需要的是一個(gè)全功能的序列。Java最開(kāi)始()提供的是一個(gè)標(biāo)準(zhǔn)繼承器,名為Enumeration(枚舉),為它的所有集合類提供服務(wù)。繼承器允許我們遍歷那個(gè)序列,同時(shí)毋需關(guān)心基礎(chǔ)結(jié)構(gòu)是什么——換言之,不管它是一個(gè)矢量、一個(gè)鏈接列表、一個(gè)堆棧,還是其他什么東西。利用這一級(jí)抽象,可將集合細(xì)節(jié)與用于訪問(wèn)那個(gè)集合的代碼隔離開(kāi)。如果想對(duì)集合中的一系列元素進(jìn)行操縱或比較,而不是僅僅面向一個(gè),這時(shí)又該怎么辦呢?辦法就是使用一個(gè)“繼續(xù)器”(Iterator),它屬于一種對(duì)象,負(fù)責(zé)選擇集合內(nèi)的元素,并把它們提供給繼承器的用戶。但在許多情況下,這樣做往往會(huì)無(wú)功而返。但將數(shù)據(jù)從集合中取出的時(shí)候,方式卻并不總是那么明顯。將某樣?xùn)|西置入集合時(shí),采用的方式是十分明顯的。其中包括集、隊(duì)列、散列表、樹(shù)、堆棧等等。例如,可以用一個(gè)矢量統(tǒng)一對(duì)所有元素的訪問(wèn)方式;一個(gè)鏈接列表則用于保證所有元素的插入統(tǒng)一。而Java也用自己的標(biāo)準(zhǔn)庫(kù)提供了集合。Object Pascal用自己的“可視組件庫(kù)”(VCL)提供集合。幸運(yùn)的是,設(shè)計(jì)優(yōu)良的OOP語(yǔ)言都配套提供了一系列集合。所以我們事先不必知道要在一個(gè)集合里容下多少東西。這種新對(duì)象通常叫作“集合”(亦叫作一個(gè)“容器”,但AWT在不同的場(chǎng)合應(yīng)用了這個(gè)術(shù)語(yǔ),所以本書(shū)將一直沿用“集合”的稱呼。當(dāng)然,也可以用數(shù)組來(lái)做同樣的事情,那是大多數(shù)語(yǔ)言都具有的一種功能。在面向?qū)ο蟮脑O(shè)計(jì)中,大多數(shù)問(wèn)題的解決辦法似乎都有些輕率——只是簡(jiǎn)單地創(chuàng)建另一種類型的對(duì)象。1 集合與繼承器針對(duì)一個(gè)特定問(wèn)題的解決,如果事先不知道需要多少個(gè)對(duì)象,或者它們的持續(xù)時(shí)間有多長(zhǎng),那么也不知道如何保存那些對(duì)象。但Java確實(shí)提供了一個(gè)垃圾收集器(Smalltalk也有這樣的設(shè)計(jì);盡管Delphi默認(rèn)為沒(méi)有垃圾收集器,但可選擇安裝;而C++亦可使用一些由其他公司開(kāi)發(fā)的垃圾收集產(chǎn)品)。當(dāng)然,垃圾收集器顯得方便得多,但要求所有應(yīng)用程序都必須容忍垃圾收集器的存在,并能默許隨垃圾收集帶來(lái)的額外開(kāi)銷。若在堆棧或者靜態(tài)存儲(chǔ)空間里創(chuàng)建一個(gè)對(duì)象,編譯器會(huì)判斷對(duì)象的持續(xù)時(shí)間有多長(zhǎng),到時(shí)會(huì)自動(dòng)“破壞”或者“清除”它。大家或許認(rèn)為既然它如此靈活,那么無(wú)論如何都應(yīng)在內(nèi)存堆里創(chuàng)建對(duì)象,而不是在堆棧中創(chuàng)建。除此以外,更大的靈活性對(duì)于常規(guī)編程問(wèn)題的解決是至關(guān)重要的。由于存儲(chǔ)空間的管理是運(yùn)行期間動(dòng)態(tài)進(jìn)行的,所以在內(nèi)存堆里分配存儲(chǔ)空間的時(shí)間比在堆棧里創(chuàng)建的時(shí)間長(zhǎng)得多(在堆棧里創(chuàng)建存儲(chǔ)空間一般只需要一個(gè)簡(jiǎn)單的指令,將堆棧指針向下或向下移動(dòng)即可)。這些參數(shù)都在程序正式運(yùn)行時(shí)才決定的。第二個(gè)方法是在一個(gè)內(nèi)存池中動(dòng)態(tài)創(chuàng)建對(duì)象,該內(nèi)存池亦叫“堆”或者“內(nèi)存堆”。然而,我們同時(shí)也犧牲了靈活性,因?yàn)樵诰帉?xiě)程序時(shí),必須知道對(duì)象的準(zhǔn)確的數(shù)量、存在時(shí)間、以及類型。這樣便為存儲(chǔ)空間的分配和釋放提供了一個(gè)優(yōu)先級(jí)。C++認(rèn)為程序的執(zhí)行效率是最重要的一個(gè)問(wèn)題,所以它允許程序員作出選擇。最重要的問(wèn)題之一是對(duì)象的創(chuàng)建及破壞方式。 it takes the same amount of time regardless of the element you select. However, in a LinkedList it is expensive to move through the list to randomly select an element, and it takes longer to find an element that is further down the list. On the other hand, if you want to insert an element in the middle of a sequence, it’s much cheaper in a LinkedList than in an ArrayList. These and other operations have different efficiencies depending on the underlying structure of the sequence. In the design phase, you might start with a LinkedList and, when tuning for performance, change to an ArrayList. Because of the abstraction via iterators, you can change from one to the other with minimal impact on your code. In the end, remember that a container is only a storage cabinet to put objects in. If that cabinet solves all of your needs, it doesn’t really matter how it is implemented (a basic concept with most types of objects). If you’re working in a programming environment that has builtin overhead due to other factors, then the cost difference between an ArrayList and a LinkedList might not matter. You might need only one type of sequence. You can even imagine the “perfect” container abstraction, which can automatically c