《數據結構與抽象:Java語言描述(原書第4版)》一2.1.3 實現核心方法
本節書摘來華章計算機《數據結構與抽象:Java語言描述(原書第4版)》一書中的第2章 ,第2.1.3節,[美]弗蘭克M.卡拉諾(Frank M. Carrano) 蒂莫西M.亨利(Timothy M. Henry) 著 羅得島大學 新英格蘭理工學院 辛運幃 饒一梅 譯 更多章節內容可以訪問雲棲社區“華章計算機”公眾號查看。
2.1.3 實現核心方法
數據域。在定義任何核心方法之前,需要考慮類的數據域。因為包要保存一組對象,所以一個域是這些對象的數組。數組的長度定義了包的容量。可以讓客戶指定這個容量,我們也可以提供一個默認容量。另外,我們想跟蹤包中當前項的個數。所以可以為我們的類定義如下的數據域:
並將它們加到圖1-2中類的UML表示中。得到的表示如圖2-2所示。
程序設計技巧:終態數組
通過聲明數組bag是類ArrayBag的一個終態數據成員,可知變量bag中的引用不能改變。雖然以這種方式聲明數組是一種好的做法,但數組元素bag[0],bag[1],…的值還是可以改變的。這樣的改變是需要的,但必須阻止客戶從bag中得到對數組的引用。這種情況會讓數組的內容易受惡意毀壞。當在後麵定義方法toArray時會進一步討論。
關於構造方法。這個類的構造方法必須創建數組bag。注意,在前麵的數據域bag的聲明中沒有創建數組。在構造方法中忘記創建數組是一個常見錯誤。為創建數組,構造方法必須指定數組的長度,這是包的容量。因為我們已經創建了一個空包,所以構造方法還應該將域numberOfEntries初始化為0。
決定在數組bag的聲明中使用泛型,影響到我們在構造方法中如何分配這個數組。如下的語句
在語法上是不正確的。當分配數組時不能使用泛型。相反,我們可以分配對象是Object類型的數組,如下所示:
產生語法錯誤,因為不能將Object[]類型的數組賦給T[]類型的數組,即兩個數組的類型不兼容。
轉型是必需的,但也會帶來它自己的問題。語句
如果再次編譯這個類,且使用選項-Xlint,則會有更詳細的信息,信息的開頭如下:
編譯程序想讓你保證將數組中的每項從類型Object轉型為泛型T都是安全的。因為數組剛剛分配,所以它含有null項。因此,轉型是安全的,故我們在有問題的語句之前寫如下的注釋可讓編譯程序忽略這個警告:
這條給編譯程序的命令隻能放在方法定義或變量聲明之前。因為賦值
注:禁止編譯警告
要禁止編譯程序給出的未檢查轉型警告,可以在標記的語句之前寫如下的語句注意,這條命令隻能放在方法定義或變量聲明之前。應該包含一條注釋,對你禁止編譯程序的警告做出解釋。
構造方法。下列構造方法執行前麵的步驟,使用參數所給出的容量:
默認構造方法可以調用前一個,將默認容量作為參數傳給它,如下所示。
我們知道,構造方法可以使用關鍵字this作為方法名來調用同一個類中的另一個構造方法。
類的框架。讓我們來看看到目前為止所定義的類。在完成了類的初始部分(即頭、數據域和構造方法)後,可以為公有方法添加注釋和頭,從BagInterface中將這些內容複製過來即可。然後在這些方法頭的後麵寫空方法。程序清單2-1顯示了這些步驟之後的結果。下一個任務是實現這三個核心方法。
程序清單2-1 類ArrayBag的框架
程序設計技巧:當定義實現接口的類時,從接口中複製它們來添加類中公有方法的注釋和頭。使用這種方式,方便你在實現時檢查每個方法的規格說明。另外,以後維護代碼的人也容易訪問這些規格說明。
設計決策:當數組bag中裝了一部分數據時,包的項應該放在數組的哪些元素中?
當向數組添加第一個項時,一般將它放在數組的第一個元素中,即下標為0的元素。不過這樣做也不是必需的,特別是對於實現集合的數組來說。例如,有些集合的實現受益於忽略下標為0的數組元素,而將下標1作為數組的第一個元素。有時你可能會想先使用數組尾端然後再使用數組頭。對於包,我們沒有理由不按常理做,所以包中的對象將從數組的下標0處開始。
另一個要考慮的問題是,包的對象是否應該保存在數組連續的元素中。要求add方法將對象連續放到數組bag中,肯定是合理的,但是為什麼我們要關心這個問題?這真是一個值得關注的問題嗎?關於計劃中的實現方案,必須確定某些事實或斷言,以便每個方法的動作不會對其他方法產生不利。例如,方法toArray必須“知道”add方法將包的項放在哪裏。我們現在的決策會影響從包中刪除一項時將發生什麼。方法remove需要保證數組項保存在連續的元素中嗎?它必須這樣做,因為至少到現在,我們仍強調包項保存在連續的數組元素中。
方法add。如果包滿了,則不能添加任何東西。這種情形下,方法add應該返回假。否則,僅需緊接在數組bag最後項的後麵添加newEntry,語句如下:
如果向空包中添加項,則numberOfEntries是0,應該給bag[0]賦值。如果包中含有一個項,則添加的項應賦給bag[1],以此類推。每次向包中添加項後,要遞增計數器numberOfEntries的值。這些步驟如圖2-3所示,且由圖後麵的add方法的定義來完成。
注意,我們調用isArrayFull就好像它已經定義過一樣。之前我們沒有把isArrayFull作為核心方法,它的使用表明它應該在核心組內。
注:包中的項沒有特定的次序。所以,方法add可以將新項放到數組bag中最方便的元素位置。在前麵那個add的定義中,元素緊接在已用的最後元素之後。
注:通常,討論中提到數組時就好像它真的含有對象一樣。實際上,Java數組含有指向對象的引用,如圖2-3所示的數組一樣。
方法isArrayFull。當包中含有的對象數與數組bag能容納的量相等時,包就滿了。當numberOfEntries等於數組容量時就發生了這種情形。所以,isArrayFull有下列簡單的定義:
方法toArray。在最初的核心組內,最後一個方法toArray是獲取包中的項,並將它們返回到客戶新分配的數組內。這個新數組的長度可以與包中項的個數(即numberOfEntries值)相等,而不是與數組bag的長度相等。但是,在分配數組時遇到了定義構造方法時遇到過的同樣問題,所以采用與構造方法相同的處理步驟。
在toArray創建新數組後,使用簡單的循環可以將數組bag中的引用複製到這個新數組中,然後返回這個數組。所以toArray的定義如下所示。
設計決策:方法toArray應該返回數組bag而不是複製嗎?
能得到指向myBag中項的數組的引用。客戶可以使用變量bagArray來顯示myBag的內容。
但是,引用bagArray是數組bag自身,即bagArray是對象myBag中的私有實例變量bag的別名,所以它能讓客戶直接訪問這個私有數據。因此客戶不調用類的公有方法就能修改包的內容。例如,如果myBag是圖2-3中所示的滿包,語句
將把項Ted改為null。如果本意是想從包中刪除Ted,雖然這個方法聽上去很好,但這樣做可能會破壞包的完整性。具體來說,數組bag中的項可能不再是連續的,且包中的項數也會出錯。
安全說明:類不應該返回指向其私有數據域的數組的引用。
注:使用泛型,可以限製集合中的項的數據類型,因為
聲明數據類型為Object的變量可以是任何數據類型對象的引用,但是有泛型數據類型的變量隻能指向指定數據類型的對象。
由Object類型的變量指向的項的集合可以含有各種相關類的對象,但由泛型變量指向的項的集合隻能含有由繼承而相關的類的對象。自測題4 在前麵的toArray方法中,numberOfEntries的值一般等於bag.length嗎?
自測題5 假定前麵的toArray方法讓新數組result的長度與數組bag相同。客戶如何得到返回的數組中的項數?
自測題6 假定前麵的toArray方法返回數據bag而不是返回像result這樣的新數組。如果myBag是含5個項的包,則下列語句對數組bag和域numberOfEntries的影響是什麼?自測題7 如果調用方法Arrays.copyOf,則方法toArray的方法體中可以含有一個return語句。修改方法toArray。
最後更新:2017-06-26 17:02:16