《数据结构与抽象:Java语言描述(原书第4版)》一2.1.7 删除项的方法
本節(jié)書摘來華章計(jì)算機(jī)《數(shù)據(jù)結(jié)構(gòu)與抽象:Java語言描述(原書第4版)》一書中的第2章 ,第2.1.7節(jié),[美]弗蘭克M.卡拉諾(Frank M. Carrano) 蒂莫西M.亨利(Timothy M. Henry) 著 羅得島大學(xué) 新英格蘭理工學(xué)院 辛運(yùn)幃 饒一梅 譯 更多章節(jié)內(nèi)容可以訪問云棲社區(qū)“華章計(jì)算機(jī)”公眾號查看。
2.1.7 刪除項(xiàng)的方法
我們將從包中刪除項(xiàng)的3個(gè)方法推遲到現(xiàn)在,因?yàn)?個(gè)方法中的一個(gè)有些困難,它涉及的查找機(jī)制與contains中用到的相似。我們從更容易定義的另兩個(gè)方法入手。
方法clear。方法clear從包中刪除所有的項(xiàng),一次刪除一個(gè)。下面這個(gè)clear的定義調(diào)用方法remove,直到包為空。
循環(huán)中每次要?jiǎng)h除哪個(gè)項(xiàng)是不重要的。所以,我們調(diào)用刪除一個(gè)不確定項(xiàng)的remove方法。另外,不保存方法返回的這個(gè)項(xiàng)。
注意,因?yàn)閞emove方法將調(diào)用checkInitialization,所以clear不需要顯式地調(diào)用它。
注:我們可以根據(jù)尚未定義的方法remove來定義方法clear。但是,在定義remove之前,我們不能完全測試clear。
自測題10 修改方法clear的定義,讓它不調(diào)用isEmpty。
提示:while語句應(yīng)該有一個(gè)空循環(huán)體。
自測題11 用下列語句
替換上一段的clear定義中的循環(huán),有什么缺點(diǎn)?
刪除不確定項(xiàng)。不帶參數(shù)的方法remove從包中刪除一個(gè)不確定項(xiàng),只要包不為空。回憶程序清單1-1所示的接口中給出的方法的規(guī)格說明,該方法返回被它刪除的項(xiàng):
如果方法執(zhí)行前包是空的,則返回null。
從包中刪除一個(gè)項(xiàng),涉及從數(shù)組中刪除它。雖然我們能訪問數(shù)組bag中的任何項(xiàng),但最后一項(xiàng)是最容易刪除的。為此,
- 訪問最后一項(xiàng),它能被返回。
- 將項(xiàng)的數(shù)組元素設(shè)置為null。
- numberOfEntries減1。
numberOfEntries減1,就會忽略最后一項(xiàng),意味著它已被高效地刪除了,即使我們沒有將它在數(shù)組中的位置設(shè)置為null。但是不要跳過這一步。
將前面的步驟轉(zhuǎn)換為Java程序,得到如下的方法定義:
安全說明:將數(shù)組元素bag[numberOfEntries???-???1]設(shè)置為null,標(biāo)記被刪除對象可進(jìn)行垃圾回收,并防止惡意代碼來訪問它。
安全說明:在正確計(jì)數(shù)后更新計(jì)數(shù)器。在前面的代碼中,刪除數(shù)組最后一項(xiàng)后才將numberOfEntries減1,即使計(jì)算了3次numberOfEntries-1。雖然下面的改進(jìn)可以避免這個(gè)重復(fù),但時(shí)間上微不足道的節(jié)省不值得冒太早減小計(jì)數(shù)器帶來的不安全
風(fēng)險(xiǎn):
不可否認(rèn),在這種情形下,數(shù)組和計(jì)數(shù)器不同步的情況還是有可能的。不管怎樣,如果邏輯更復(fù)雜,則數(shù)組處理過程中可能會發(fā)生異常。這個(gè)中斷將導(dǎo)致已更新的計(jì)數(shù)器不準(zhǔn)確。
自測題12 為什么方法remove將從數(shù)組bag中刪除的項(xiàng)替換為null?
自測題13 前一個(gè)remove方法刪除數(shù)組bag中的最后一項(xiàng)。刪除其他項(xiàng)為什么會更難完成?
刪除給定的項(xiàng)。從包中刪除項(xiàng)的第3個(gè)方法涉及刪除給定項(xiàng)(稱為anEntry)的方法。如果項(xiàng)在包中出現(xiàn)多次,則僅刪除它的一次出現(xiàn)。沒有指定刪除哪次出現(xiàn)。我們只刪除查找時(shí)遇到的anEntry的首次出現(xiàn)。正如我們在1.2節(jié)中所討論的,方法將根據(jù)在包中是否找到這個(gè)項(xiàng)而返回真或假。
假定包不為空,則查找數(shù)組bag與方法contains所做的一樣。如果anEntry等于bag[index],則記下index的值。圖2-4說明成功查找后的數(shù)組。
現(xiàn)在需要?jiǎng)h除bag[index]中的項(xiàng)。如果只寫
則刪除bag[index]中指向的項(xiàng)的引用,但數(shù)組中會留下空隙。即包的內(nèi)容不再占據(jù)連續(xù)的數(shù)組位置,如圖2-5a所示。我們可以移動隨后的項(xiàng)來去掉這個(gè)空隙,如圖2-5b所示,并將指向最后項(xiàng)的重復(fù)引用替換為null,如圖2-5c所示。但不一定用這個(gè)費(fèi)時(shí)的方法。
記住,我們不需要維護(hù)包中項(xiàng)的具體次序。所以刪除項(xiàng)后,不是移動數(shù)組項(xiàng),而是用數(shù)組中最后面的項(xiàng)替換被刪除的項(xiàng),如圖2-6所示。找到bag[index]中的anEntry后,如圖2-6a所示,將bag[numberOfEntries-1]中的項(xiàng)復(fù)制到bag[index]中(見圖2-6b)。然后將bag[numberOfEntries-1]中的項(xiàng)替換為null,如圖2-6c所示,最后numberOfEntries減1。
刪除給定項(xiàng)的偽代碼。現(xiàn)在將前面的討論用偽代碼寫出來,對給定的項(xiàng)anEntry,從含有它的包中刪除:
這個(gè)偽代碼假定包中含有anEntry。
在偽代碼中添加一些細(xì)節(jié),以適應(yīng)anEntry不在包中的情形,偽代碼如下:
避免重復(fù)工作。很容易將這段偽代碼翻譯為Java方法remove。但是,如果我們這樣做了,就會發(fā)現(xiàn)新方法與刪除不確定項(xiàng)中寫過的remove方法有很多類似的地方。實(shí)際上,如果anEntry出現(xiàn)在bag[numberOfEntries-1]處,則這兩個(gè)remove方法將有完全相同的效果。為避免這樣的重復(fù)勞動,兩個(gè)remove方法可以調(diào)用一個(gè)私有方法來完成刪除操作。可以如下說明一個(gè)方法:
因?yàn)檫@是一個(gè)私有方法,所以類中的其他方法可以給它傳一個(gè)下標(biāo)作為參數(shù),仍能讓這個(gè)下標(biāo)(實(shí)現(xiàn)細(xì)節(jié))對類的客戶隱藏。
在實(shí)現(xiàn)這個(gè)私有方法之前,讓我們看看是否可以用它來修改“刪除不確定項(xiàng)”中的remove方法。因?yàn)樵摲椒▌h除并返回?cái)?shù)組bag的最后一項(xiàng),即bag[numberOfEntries-1],所以它的定義中可以調(diào)用removeEntry(numberOfEntries-1)。繼續(xù)我們的工作,就如同removeEntry已經(jīng)定義且測試過一樣,可以如下定義remove:
這個(gè)定義看上去不錯(cuò),我們來實(shí)現(xiàn)第二個(gè)remove方法。
第二個(gè)remove方法。第一個(gè)remove方法不查找要?jiǎng)h除的項(xiàng),因?yàn)樗鼊h除數(shù)組中的最后一項(xiàng)。但第二個(gè)remove方法必須執(zhí)行查找。現(xiàn)在先不考慮在數(shù)組中查找一個(gè)項(xiàng)的細(xì)節(jié),我們將這個(gè)任務(wù)委派給另一個(gè)私有方法來完成,它的規(guī)格說明如下所示。
假定這個(gè)私有方法已經(jīng)定義且測試過,我們在第二個(gè)remove方法中調(diào)用它,如下所示。
注意,removeEntry返回它刪除的項(xiàng)或者null。這正是第一個(gè)remove方法所需要的,但第二個(gè)remove方法必須返回一個(gè)布爾值。所以,在第二個(gè)方法中,我們必須將想刪除的項(xiàng)與removeEntry的返回值進(jìn)行比較,以便得到希望的布爾值。
自測題14 remove的前一個(gè)定義中的return語句能寫成下面這樣嗎?
自測題15 ArrayBag中的數(shù)組bag含有包aBag中的項(xiàng)。如果數(shù)組含有字符串"A","A","B","A","C",為什么aBag.remove("B")將數(shù)組的內(nèi)容改變?yōu)?#34;A","A","C","A",null,而不是"A","A","A","C",null或"A","A",null,"A","C"?
私有方法removeEntry的定義。現(xiàn)在回過頭來看在“刪除給定項(xiàng)的偽代碼”中為從包中刪除指定項(xiàng)而寫的偽代碼。私有方法removeEntry假定項(xiàng)的查找已經(jīng)完成,所以可以忽略偽代碼的第一步。不管怎樣,偽代碼的其他部分給出了刪除一個(gè)項(xiàng)的基本邏輯。可以修改偽代碼,如下所示。
前一段給出的第二個(gè)remove方法的定義將getIndexOf返回的整數(shù)傳給removeEntry。因?yàn)間etIndexOf可能返回-1,所以removeEntry必須對這樣一個(gè)參數(shù)值進(jìn)行查找。因此,如果包不為空(即如果numberOfEntries大于0)且givenIndex大于或等于0,則removeEntry刪除位于givenIndex的數(shù)組項(xiàng),將它用最后一項(xiàng)來替換,并讓numberOfEntries減1。然后該方法返回刪除的項(xiàng)。但是,如果包是空的,則該方法返回null。
該方法的代碼如下所示。
找到要?jiǎng)h除的項(xiàng)。現(xiàn)在需要考慮如何在包中找到要?jiǎng)h除的項(xiàng),這樣才可以將它的下標(biāo)傳給removeEntry。方法contains執(zhí)行與remove的定義中查找anEntry相同的機(jī)制。不幸的是,contains返回真或假,它不返回項(xiàng)在數(shù)組中的下標(biāo)。所以在定義這個(gè)方法時(shí)不能簡單調(diào)用那個(gè)方法。
設(shè)計(jì)決策:方法contains應(yīng)該返回找到項(xiàng)的下標(biāo)嗎?
我們應(yīng)該修改contains的定義,讓它返回一個(gè)下標(biāo)而不是一個(gè)布爾值嗎?不應(yīng)該。作為一個(gè)公有方法,contains不應(yīng)該給客戶提供這樣的實(shí)現(xiàn)細(xì)節(jié)。客戶不應(yīng)該期望得到放在數(shù)組中的包的項(xiàng),因?yàn)樗鼈儧]有具體的次序。不應(yīng)改變contains的規(guī)格說明,而是應(yīng)該遵循最初的規(guī)劃,定義一個(gè)私有方法來查找一個(gè)項(xiàng)并返回它的下標(biāo)。
getIndexOf的定義。getIndexOf的定義與contains的定義很像,我們記得方法contains中它的循環(huán)是這樣的:
這個(gè)循環(huán)結(jié)構(gòu)適合于方法getIndexOf,但當(dāng)找到項(xiàng)時(shí)必須保存index的值。該方法將返回這個(gè)下標(biāo)而不是一個(gè)布爾值。
為修改前面這個(gè)循環(huán)以便將其用在getIndexOf中,我們定義一個(gè)整數(shù)變量where來記錄當(dāng)anEntry等于bag[index]時(shí)index的值。所以getIndexOf是這樣的:
方法getIndexOf返回where的值。注意,我們將where初始化為-1,這是沒找到anEntry時(shí)返回的值。
自測題16 在方法getIndexOf的return語句之前,能添加什么assert語句來表示方法能夠返回的可能值?
自測題17 修改方法getIndexOf的定義,讓它不使用布爾值。
旁白:正向思考
與方法contains不一樣,方法getIndexOf將布爾變量found僅用來控制循環(huán),而不是作為返回值。所以我們可以修改邏輯,避免取反操作符!的使用。
我們使用變量stillLooking來替代found,并將它初始化為真。然后可以將布爾表達(dá)式!found替換為stillLooking,如你在下面的方法getIndexOf的定義中所見:
如果在數(shù)組中找到anEntry,則將stillLooking設(shè)置為假來結(jié)束循環(huán)。有些程序員傾向于正向思考,如這個(gè)版本中所述,而另一些人覺得!found就已經(jīng)非常清楚了。
方法contains定義的修改。完成remove和它們調(diào)用的私有方法的定義后,我們知道方法contains可以調(diào)用私有方法getIndexOf,得到比方法contains中更簡單的定義。我們記得,如果anEntry在包中,則表達(dá)式getIndexOf(anEntry)返回0~numberOfEntries-1的一個(gè)整數(shù),否則返回-1。即如果anEntry在包中,則getIndexOf(anEntry)大于-1。所以可以定義contains如下:
因?yàn)橐呀?jīng)修改了contains的定義,所以應(yīng)該再次測試它。為此,我們還要測試私有方法getIndexOf。
注:方法contains和remove都必須執(zhí)行類似的對項(xiàng)的查找。將查找功能單獨(dú)放在方法contains和remove都能調(diào)用的一個(gè)私有方法中,使得我們的代碼更易調(diào)試及維護(hù)。這個(gè)策略與在兩個(gè)remove方法都調(diào)用的私有方法removeEntry中定義刪除操作時(shí)用到的一樣。
設(shè)計(jì)決策:什么方法應(yīng)該調(diào)用checkInitialization?
類ArrayBag的關(guān)鍵點(diǎn)是數(shù)組bag的分配。你已經(jīng)看到,像add這樣依賴于這個(gè)數(shù)組的方法,是從調(diào)用checkInitialization開始的,以確保構(gòu)造方法已經(jīng)完全初始化了ArrayBag對象,包括數(shù)組的分配。而我們可以堅(jiān)持在直接涉及數(shù)組bag的每個(gè)方法中調(diào)用checkInitialization,不過我們選擇更加靈活的做法。例如,私有方法getIndexOf和removeEntry直接訪問bag,但它們不調(diào)用checkInitialization。為什么?刪除給定項(xiàng)的remove方法調(diào)用getIndexOf和removeEntry。如果這兩個(gè)私有方法都調(diào)用checkInitialization,則它被公有方法調(diào)用兩次。所以,對于這個(gè)具體實(shí)現(xiàn)來說,我們在公有方法中調(diào)用checkInitialization,并為這兩個(gè)私有方法添加一個(gè)前置條件來說明checkInitialization必須要先調(diào)用。因?yàn)樗鼈兪撬接蟹椒?#xff0c;所以這樣的前置條件只給我們這些實(shí)現(xiàn)者和維護(hù)者使用。一旦做了這個(gè)決策,其他的remove方法和contains方法都必須調(diào)用checkInitialization,因?yàn)樗鼈兌贾徽{(diào)用這兩個(gè)私有方法中的一個(gè)。
注意,私有方法getIndexOf和removeEntry都執(zhí)行一個(gè)已定義的任務(wù)。它們不再為第二個(gè)任務(wù)(檢查初始化)負(fù)責(zé)。
程序設(shè)計(jì)技巧:即使可能已經(jīng)有了方法的正確定義,但如果你想到了一個(gè)更好的實(shí)現(xiàn),也不要猶豫地去修改它。肯定要再次測試方法!
測試。我們的ArrayBag類基本上完成了。可以使用前面測試remove和clear的測試方法了——我們假定它們是正確的。從一個(gè)沒滿的包開始,在線程序ArrayBagDemo3刪除包中的項(xiàng)直到它為空時(shí)為止。它還包括了從滿包開始的類似的測試。最后,應(yīng)該將前面的測試整合起來再次運(yùn)行它們。在本書在線網(wǎng)站的源代碼中,測試程序是ArrayBagDemo,完整的類是ArrayBag。
總結(jié)
以上是生活随笔為你收集整理的《数据结构与抽象:Java语言描述(原书第4版)》一2.1.7 删除项的方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机计算资产分析表,财务指标计算器.x
- 下一篇: java微信红包开发_Java实现微信发