對我來說,集合類屬於最強大的一種工具,特別適合在原創編程中使用。大家可能已感覺到我對Java 1.1提供的集合多少有點兒失望。因此,看到Java 1.2對集合重新引起了正確的注意後,確實令人非常愉快。這個版本的集合也得到了完全的重新設計(由Sun公司的Joshua Bloch)。我認為新設計的集合是Java 1.2中兩項最主要的特性之一(另一項是Swing庫,將在第13章敘述),因為它們極大方便了我們的編程,也使Java變成一種更成熟的編程系統。
有些設計使得元素間的結合變得更緊密,也更容易讓人理解。例如,許多名字都變得更短、更明確了,而且更易使用;類型同樣如此。有些名字進行了修改,更接近於通俗:我感覺特別好的一個是用“反復器”(Inerator)代替了“枚舉”(Enumeration)。
此次重新設計也加強了集合庫的功能。現在新增的行為包括鏈接列表、隊列以及撤消組隊(即“雙終點隊列”)。
集合庫的設計是相當困難的(會遇到大量庫設計問題)。在C++中,STL用多個不同的類來覆蓋基礎。這種做法比起STL以前是個很大的進步,那時根本沒做這方面的考慮。但仍然沒有很好地轉換到Java裡面。結果就是一大堆特別容易混淆的類。在另一個極端,我曾發現一個集合庫由單個類構成:colleciton,它同時作為Vector和Hashtable使用。新集合庫的設計者則希望達到一種新的平衡:實現人們希望從一個成熟集合庫上獲得的完整功能,同時又要比STL和其他類似的集合庫更易學習和使用。這樣得到的結果在某些場合顯得有些古怪。但和早期Java庫的一些決策不同,這些古怪之處並非偶然出現的,而是以復雜性作為代價,在進行仔細權衡之後得到的結果。這樣做也許會延長人們掌握一些庫概念的時間,但很快就會發現自己很樂於使用那些新工具,而且變得越來越離不了它。
新的集合庫考慮到了“容納自己對象”的問題,並將其分割成兩個明確的概念:
(1) 集合(Collection):一組單獨的元素,通常應用了某種規則。在這裡,一個List(列表)必須按特定的順序容納元素,而一個Set(集)不可包含任何重復的元素。相反,“包”(Bag)的概念未在新的集合庫中實現,因為“列表”已提供了類似的功能。
(2) 映射(Map):一系列“鍵-值”對(這已在散列表身上得到了充分的體現)。從表面看,這似乎應該成為一個“鍵-值”對的“集合”,但假若試圖按那種方式實現它,就會發現實現過程相當笨拙。這進一步證明了應該分離成單獨的概念。另一方面,可以方便地查看Map的某個部分。只需創建一個集合,然後用它表示那一部分即可。這樣一來,Map就可以返回自己鍵的一個Set、一個包含自己值的List或者包含自己“鍵-值”對的一個List。和數組相似,Map可方便擴充到多個“維”,毋需涉及任何新概念。只需簡單地在一個Map裡包含其他Map(後者又可以包含更多的Map,以此類推)。
Collection和Map可通過多種形式實現,具體由編程要求決定。下面列出的是一個幫助大家理解的新集合示意圖:
這張圖剛開始的時候可能讓人有點兒摸不著頭腦,但在通讀了本章以後,相信大家會真正理解它實際只有三個集合組件:Map,List和Set。而且每個組件實際只有兩、三種實現方式(注釋⑥),而且通常都只有一種特別好的方式。只要看出了這一點,集合就不會再令人生畏。
⑥:寫作本章時,Java 1.2尚處於β測試階段,所以這張示意圖沒有包括以後會加入的TreeSet。
虛線框代表“接口”,點線框代表“抽象”類,而實線框代表普通(實際)類。點線箭頭表示一個特定的類准備實現一個接口(在抽象類的情況下,則是“部分”實現一個接口)。雙線箭頭表示一個類可生成箭頭指向的那個類的對象。例如,任何集合都可以生成一個反復器(Iterator),而一個列表可以生成一個ListIterator(以及原始的反復器,因為列表是從集合繼承的)。
致力於容納對象的接口是Collection,List,Set和Map。在傳統情況下,我們需要寫大量代碼才能同這些接口打交道。而且為了指定自己想使用的准確類型,必須在創建之初進行設置。所以可能創建下面這樣的一個List:
List x = new LinkedList();
當然,也可以決定將x作為一個LinkedList使用(而不是一個普通的List),並用x負載准確的類型信息。使用接口的好處就是一旦決定改變自己的實施細節,要做的全部事情就是在創建的時候改變它,就象下面這樣:
List x = new ArrayList();
其余代碼可以保持原封不動。
在類的分級結構中,可看到大量以“Abstract”(抽象)開頭的類,這剛開始可能會使人感覺迷惑。它們實際上是一些工具,用於“部分”實現一個特定的接口。舉個例子來說,假如想生成自己的Set,就不是從Set接口開始,然後自行實現所有方法。相反,我們可以從AbstractSet繼承,只需極少的工作即可得到自己的新類。盡管如此,新集合庫仍然包含了足夠的功能,可滿足我們的幾乎所有需求。所以考慮到我們的目的,可忽略所有以“Abstract”開頭的類。
因此,在觀看這張示意圖時,真正需要關心的只有位於最頂部的“接口”以及普通(實際)類——均用實線方框包圍。通常需要生成實際類的一個對象,將其上溯造型為對應的接口。以後即可在代碼的任何地方使用那個接口。下面是一個簡單的例子,它用String對象填充一個集合,然後打印出集合內的每一個元素:
//: SimpleCollection.java // A simple example using the new Collections package c08.newcollections; import java.util.*; public class SimpleCollection { public static void main(String[] args) { Collection c = new ArrayList(); for(int i = 0; i < 10; i++) c.add(Integer.toString(i)); Iterator it = c.iterator(); while(it.hasNext()) System.out.println(it.next()); } } ///:~
新集合庫的所有代碼示例都置於子目錄newcollections下,這樣便可提醒自己這些工作只對於Java 1.2有效。這樣一來,我們必須用下述代碼來調用程序:
java c08.newcollections.SimpleCollection
采用的語法與其他程序是差不多的。
大家可以看到新集合屬於java.util庫的一部分,所以在使用時不需要再添加任何額外的import語句。
main()的第一行創建了一個ArrayList對象,然後將其上溯造型成為一個集合。由於這個例子只使用了Collection方法,所以從Collection繼承的一個類的任何對象都可以正常工作。但ArrayList是一個典型的Collection,它代替了Vector的位置。
顯然,add()方法的作用是將一個新元素置入集合裡。然而,用戶文檔謹慎地指出add()“保證這個集合包含了指定的元素”。這一點是為Set作鋪墊的,後者只有在元素不存在的前提下才會真的加入那個元素。對於ArrayList以及其他任何形式的List,add()肯定意味著“直接加入”。
利用iterator()方法,所有集合都能生成一個“反復器”(Iterator)。反復器其實就象一個“枚舉”(Enumeration),是後者的一個替代物,只是:
(1) 它采用了一個歷史上默認、而且早在OOP中得到廣泛采納的名字(反復器)。
(2) 采用了比Enumeration更短的名字:hasNext()代替了hasMoreElement(),而next()代替了nextElement()。
(3) 添加了一個名為remove()的新方法,可刪除由Iterator生成的上一個元素。所以每次調用next()的時候,只需調用remove()一次。
在SimpleCollection.java中,大家可看到創建了一個反復器,並用它在集合裡遍歷,打印出每個元素。