Java——容器類庫框架淺析
Java容器類庫概覽

Java容器類庫的主要作用是“保存對象”,我們將其劃分成以下兩個不同的概念:
Collection
一個獨(dú)立的元素序列(一種存放一組對象的方式),這些元素都服從一條或多條規(guī)則。List必須按照插入的順序保存元素;Set中不能有重復(fù)的元素;Queue按排隊(duì)規(guī)則來確定對象產(chǎn)生的順序。
Collection是一個高度抽象的容器接口,其中包含了容器的基本操作和屬性。
Map
一組成對的“鍵值對”對象,允許你使用鍵來查找值。
框架類圖中還包含了許多Abstract類,主要方便于我們創(chuàng)建容器的實(shí)例,Abstract類中已基本實(shí)現(xiàn)了接口中的方法,我們只需要選擇我們需要的方法進(jìn)行覆蓋即可。
Iterator
我們再來看Iterator,我們通常是使用Iterator迭代器來遍歷容器。上圖存在的Collection依賴于Iterator是指:實(shí)現(xiàn)Collection需要實(shí)現(xiàn)iterator()函數(shù),可以返回一個Iterator對象。ListIterator是專門用于遍歷List的迭代器。
工具類Arrays和Collections為容器添加元素
java.util包中的Arrays和Collections類中包含了很多的實(shí)用方法。Arrays類中包含操作數(shù)組的各種方法,還包含一個靜態(tài)的Arrays.asList()方法接受一個數(shù)組或是用逗號分隔的元素列表,將其轉(zhuǎn)換成一個列表對象。Collection類包含對集合操作的各種方法。我們也可以使用Collections.addAll()想容器中添加一組元素。Collections.addAll()接受一個Collection對象以及一個數(shù)組或者用逗號分隔的元素列表,將元素添加到Collection對象中。
Arrays.asList()的底層實(shí)現(xiàn)是一個數(shù)組,即使用Arrays.asList()生成的List的尺寸是不可以修改的(添加或刪除元素),否則將會拋出UnsupportedOperationException異常。
List
List接口繼承自Collection接口,用于Collection中的所有方法,在Collection的基礎(chǔ)上也添加了許多方法,使得可以在List中插入和刪除元素。List有兩種基本的實(shí)現(xiàn):ArrayList和LinkedList
- 基本的ArrayList,它適合于隨機(jī)訪問元素,但是在插入和刪除元素時就比較慢
- LinkedList適合于在元素插入和刪除較頻繁時使用,隨機(jī)訪問的速度比較慢
Set
Set中不保存重復(fù)的元素,含義同數(shù)學(xué)概念上的集合。 Set常用于測試歸屬性,即查詢某個元素是否在某個Set中。正因?yàn)槿绱瞬檎乙簿统闪薙et中重要的操作。通常會選擇HashSet的實(shí)現(xiàn),它對快速查找進(jìn)行了優(yōu)化。Set也有多種不同的實(shí)現(xiàn),不同的Set實(shí)現(xiàn)不僅具有不同的行為,而且它們對于可以在特定的Set中防止元素的類型也有不同的要求。
-
Set(interface)
存入Set的每個元素必須是唯一對的。加入Set的元素必須定義equals()方法以確保對象的唯一性。Set接口不保證維護(hù)元素的次序。
-
HashSet
為快速查找而設(shè)計(jì)的Set。存入HashSet的元素必須定義hashCode()。使用HashMap實(shí)現(xiàn)。
-
TreeSet
保持次序的Set,底層為樹結(jié)構(gòu)。使用它可以從Set中提取有序的序列。元素必須實(shí)現(xiàn)Comparable接口。使用TreeSet實(shí)現(xiàn)。
-
LinkedHashSet
具有HashSet的查詢速度,且內(nèi)部使用鏈表維護(hù)元素的順序(插入的次序)。在使用迭代器遍歷該Set時,結(jié)果會按照元素插入的次序顯示。元素也必須定義hashCode()方法
Map
Map有以下特點(diǎn):
-
Map是將鍵映射到值的鍵值對(key-value)接口
-
映射中不能包含重復(fù)的鍵,每個鍵最多可以映射到一個值,但是一個值可以被多個鍵映射
-
Map提供了三個Set視圖供我們訪問:鍵的Set、值的Set和鍵值對的Set
-
映射的順序定義為訪問的映射Set上的迭代器返回元素的順序。TreeMa類,可以對映射的順序做出特定保證;其他的,則不能保證
-
可變對象作為映射鍵需要非常小心
-
Map的實(shí)現(xiàn)類應(yīng)該提供兩個“標(biāo)準(zhǔn)“構(gòu)造函數(shù)
第一個,void(無參數(shù))構(gòu)造方法,用于創(chuàng)建空映射
第二個,帶有單個 Map 類型參數(shù)的構(gòu)造方法,用于創(chuàng)建一個與其參數(shù)具有相同鍵-值映射關(guān)系的新映射。帶有單個 Map 類型參數(shù)的構(gòu)造方法,用于創(chuàng)建一個與其參數(shù)具有相同鍵-值映射關(guān)系的新映射
Map的幾種基本實(shí)現(xiàn):
-
HashMap
Map是基于散列表的實(shí)現(xiàn)(取代了HashTable)。HashMap使用散列碼(對象hashCode()生成的值)來進(jìn)行快速搜索。
-
LinkedHashMap
類似于HashMap,但是迭代的時候,取得鍵值對的順序是起插入的順序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點(diǎn),而迭代訪問的時候更快,因?yàn)槭褂面湵砭S護(hù)了內(nèi)部次序。
-
TreeMap
基于紅黑樹的實(shí)現(xiàn)。查看“鍵”或者“鍵值對”時,它們會被排序(次序由Comparable或Comparator決定)。TreeMap的特點(diǎn)在于所得到的結(jié)果是經(jīng)過排序的。TreeMap是唯一的帶有subMap()的Map,可以返回一個子樹。
-
WeakHashMap
弱鍵(weak key)映射,允許釋放映射所指向的對象,這是為了解決某類特殊問題而設(shè)計(jì)的。如果映射之外沒有引用指向某個“鍵”,則此“鍵”可以被垃圾回收。
-
ConcurrentHashMap
一種線程安全的Map,不涉及同步加鎖。在并發(fā)中還會介紹。
Stack和Queue
Stack是一個先進(jìn)后出(LIFO)的容器。往盒子中放書,先放進(jìn)去的最后才拿得出來,最后放進(jìn)去的第一個就可以取出,這種模型就是棧(Stack)可以描述的。LinkedList中有可以實(shí)現(xiàn)棧所有功能的方法,有時也可以直接將LinkedList作為棧使用。
隊(duì)列是一個典型的先進(jìn)先出(FIFO)的容器。事物放進(jìn)容器的順序和取出的順序是相同的(優(yōu)先級隊(duì)列根據(jù)事物優(yōu)先級出隊(duì)事物)。隊(duì)列常被當(dāng)做一種可靠的將對象從程序的某個區(qū)域傳輸?shù)搅硪粋€區(qū)域的途徑。隊(duì)列在并發(fā)編程中特別重要。同樣,LinkedList也提供了方法支持隊(duì)列的行為,并且它實(shí)現(xiàn)了Queue接口。
優(yōu)先級隊(duì)列PriorityQueue
先進(jìn)先出描述了典型的隊(duì)列規(guī)則。隊(duì)列規(guī)則是指在給定一組隊(duì)列的元素情況下,確定下一個彈出隊(duì)列的元素的規(guī)則。優(yōu)先級隊(duì)列聲明的下一個彈出的元素是最需要的元素(具有最高優(yōu)先級的元素)。
我們可以在PriorityQueue上調(diào)用offer()方法來插入一個對象,這個對象就會在隊(duì)列中被排序,默認(rèn)排序?yàn)樽匀慌判?,即按插入的先后進(jìn)行排序,但是我們可以通過提供自己的Comparator來修改這個排序。當(dāng)調(diào)用peek()、poll()和remove()方法時,將獲取隊(duì)列優(yōu)先級最高的元素。
優(yōu)先級隊(duì)列算法實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通常是一個堆。
迭代器
對于訪問容器而言,有沒有一種方式使得同一份遍歷的代碼可以適用于不同類型的容器?要實(shí)現(xiàn)這樣的目的就可以使用迭代器。使用迭代器對象,遍歷并選擇序列中的對象,而客戶端不必知道或關(guān)心該序列底層的結(jié)構(gòu)。Java中對迭代器有一些限制,比如Java的Iterator只能單向移動,這個Iterator只能用來:
- 使用next()方法獲得序列的下一個元素
- 使用hasNext()方法檢查序列中是否還有元素
- 使用remove()方法將迭代器新近返回的元素刪除,意味著在調(diào)用remove()之前必須先調(diào)用next()

API中的Iterator接口中方法如上,實(shí)現(xiàn)Iterator對象需要實(shí)現(xiàn)hashNext()方法和next()方法,remove方法是一個可選操作。forEachRemaining是Java 1.8(Java SE8)中加入的方法,用于Lambda表達(dá)式。
舉一個簡單的使用迭代器訪問容器的例子:
class Cat{
private static int counter = 0;
private int id = counter++;
@Override
public String toString() {
return "Cat: " + id;
}
}
public class IteratorAccessContainer {
//不包含任何容器類型信息的遍歷容器方法
public static void showElement(Iterator it) {
while (it.hasNext()) { //hasNext()檢查序列中是否還有元素
Cat cat = it.next(); //next()返回序列中的元素
System.out.print(cat + " ");
}
System.out.println();
}
public static void main(String[] args) {
ArrayList cats1 = new ArrayList();
LinkedList cats2 = new LinkedList<>(); //可以省略類型參數(shù) 編譯器可自動推斷出
HashSet cats3 = new HashSet<>();
for(int i=0;i<3; i++) {
cats1.add(new Cat());
cats2.add(new Cat());
cats3.add(new Cat());
}
showElement(cats1.iterator());
showElement(cats2.iterator());
showElement(cats3.iterator());
}
}
/*
output:
Cat: 0 Cat: 3 Cat: 6
Cat: 1 Cat: 4 Cat: 7
Cat: 2 Cat: 8 Cat: 5
*/
showElement()方法不包含任何有關(guān)它遍歷的序列類型信息,這就展示了Iterator的好處:能夠?qū)⒈闅v序列的操作與序列底層結(jié)構(gòu)分離。也可以說,迭代器統(tǒng)一了對容器的訪問方式。
從容器框架圖中我們可以看出,Collection是描述所有序列容器的共性的根接口。但是在C++中,標(biāo)準(zhǔn)的C++類庫中沒有其他容器的任何公共基類,容器之間的共性都是通過迭代器達(dá)成的。在Java中,則將兩種方法綁定到了一起,實(shí)現(xiàn)Collection的同時也要實(shí)現(xiàn)iterator()方法(返回該容器的迭代器)。
ListIterator
ListIterator是一個更加強(qiáng)大的Iterator子類型,但是它只能用于各種List的訪問。Iterator只能前向移動,但ListIterator允許我們可以前后移動。它還可以產(chǎn)生相對于迭代器在列表中指向當(dāng)前位置的前一個和后一個索引,并且可以使用set()方法替換它訪問過的最后一個元素。remove()方法可以刪除它訪問過的最后一個元素。需要注意,這兩處的最后一個元素只的都是調(diào)用next()或者previous返回的元素,也就意味著調(diào)用set()、remove()這兩個方法之前,要先調(diào)用next()或者previous()。
需要注意ListIterator在序列中的游標(biāo)位置與Iterator不同,Iterator的游標(biāo)位置始終位于調(diào)用previous()將返回的元素和調(diào)用next()將返回的元素之間。長度為n的列表的迭代器的游標(biāo)位置有n+1個。

使用ListIterator對列表進(jìn)行正向和返回迭代,以及使用set()替換列表元素的例子:
public class ListIteration {
public static void main(String[] args) {
List catList = new ArrayList<>();
for(int i=0; i<5; i++) {
catList.add(new Cat());
}
ListIterator it = catList.listIterator();
System.out.println("CatNo. nextIndex previousIndex");
//正向遍歷
System.out.println("正向遍歷:");
while (it.hasNext()) {
Cat cat = it.next();
System.out.println(cat+" "+it.nextIndex()+" "+it.previousIndex());
}
System.out.println();
System.out.println("當(dāng)?shù)饔螛?biāo)處于最后一個元素末尾時:");
ListIterator it2 = catList.listIterator();
while (it2.hasNext()) {
Cat cat = it2.next();
System.out.println(cat+" "+it.nextIndex()+" "+it.previousIndex());
}
System.out.println();
//反向遍歷
System.out.println("反向遍歷");
while(it.hasPrevious()) {
Cat cat = it.previous();
System.out.println(cat+" "+it.nextIndex()+" "+it.previousIndex());
}
System.out.println();
//產(chǎn)生指定游標(biāo)位置的迭代器 從第二個位置開始向前替換列表中的Cat對象
System.out.println("從第二個位置開始向前替換列表中的Cat對象");
it = catList.listIterator(2);
while(it.hasNext()) {
it.next();
it.set(new Cat());
}
System.out.println(catList);
}
}
/*
CatNo. nextIndex previousIndex
正向遍歷:
Cat: 0 1 0
Cat: 1 2 1
Cat: 2 3 2
Cat: 3 4 3
Cat: 4 5 4
當(dāng)?shù)饔螛?biāo)處于最后一個元素末尾時:
Cat: 0 5 4
Cat: 1 5 4
Cat: 2 5 4
Cat: 3 5 4
Cat: 4 5 4
反向遍歷
Cat: 4 4 3
Cat: 3 3 2
Cat: 2 2 1
Cat: 1 1 0
Cat: 0 0 -1
從第二個位置開始向前替換列表中的Cat對象
[Cat: 0, Cat: 1, Cat: 5, Cat: 6, Cat: 7]
*/
foreach與迭代器
foreach語法不僅可以用在數(shù)組,也可以用在任何Collection對象。之所以可以用在Collection對象,是因?yàn)镴ava SE5引入了Iterable接口,該接口包含一個能夠產(chǎn)生Iterator的iterator()方法,并且Iterable接口被foreach用來在序列中移動。因此,如果創(chuàng)建了任何實(shí)現(xiàn)Iterable的類,都可以將它用于foreach當(dāng)中。需要注意,數(shù)組雖然可以使用foreach語法遍歷,但不意味著數(shù)組是Iterable的。
實(shí)現(xiàn)一個可迭代的類,使用foreach方法遍歷
public class IterableClass implements Iterable<String>{
private String[] words = ("This is happy day.").split(" ");
@Override
public Iterator iterator() {
return new Iterator() {
private int index = 0;
//判斷是否存在下一個元素
public boolean hasNext() {
return index < words.length;
}
//返回下一個元素
public String next() {
return words[index++];
}
public void remove() { //remove可以不用實(shí)現(xiàn)
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
//foreach語法遍歷實(shí)現(xiàn)了Iterable接口的類
for(String s : new IterableClass()) {
System.out.println(s);
}
}
}
/*
This
is
happy
day.
*/
