1 概述
Java提供了一個豐富的集合框架,這個集合框架包含了許多接口、虛擬類和實現類。這些接口和類提供了豐富的功能,能夠滿足基本的聚合需求。下圖就是這個框架的整體結構圖:
可以看見,這個框架非常大,大到吃驚的地步。這個圖的左面部分是接口,右面部分是類,中間的線代表了右面的類實現了左面的哪些接口。比如,AbstractList類實現了List接口,那麼繼承自AbstractList類的子類都實現了這個接口。還有,如果一個類實現了一個接口,那麼這個類也實現了這個接口的所有父接口。比如,TreeSet類實現了Deque接口,那麼TreeSet類也實現了Queue接口、Collection接口和Iterable接口(接口Collection擴展了Iterable,這裡沒顯示出來)。
圖中紅色的類表示抽象類,大部分抽象類都以Abstract開頭,除了Dictionary。綠顏色的類表示遺留類,這些類在Java一開始的時候就已經存在了。
Java集合類庫中將接口(interface)與實現(implementation)分離。這裡以隊列(queue)為例。
隊列接口指出可以在隊列的尾部添加元素,在隊列的頭部刪除元素,並且可以查找隊列中的元素個數。即隊列中的元素按照先進先出的元素使用隊列。
如果我們設計queue的接口,可能這樣設計:
interface Queue{ void add(E element); E remove(); int size(); }
使用循環數組時需要指出對頭head和隊尾tail;使用鏈表時也需要給出頭和尾:
class CircularArrayQueueimplements Queue { CircularArrayQueue(int capacity){...} public void add(E element){...} public E remove(){...} private E[] elements; private int head; private int tail; } class LinkedListQueue implements Queue { LinkedListQueue(){...} public void add(E element){...} public E remove(){...} public int size(){...} private Link head; private Link tail; }
當在程序中使用隊列時,一旦構建了集合就不需要知道究竟使用了哪種實現。因此,只有在構建集合對象時,使用具體的類才有意義。可以使用接口類型存放集合的引用:
Queueemployee=new CircularArrayQueue<>(100); employee.add(new Employee("Harry"));
Queueemployee=new LinkedListQueue<>(); employee.add(new Employee("Harry"));
在上圖中,我們可以發現很多紅色的以Abstract開頭的類,這些類都是抽象類,這些類是為類庫的實現者設計的,這些類中實現了一些基本的方法。如果想要實現自己的隊列類,你會發現擴展AbstractQueue類比實現Queue接口更方便。
在Java類庫中,集合類的基本接口是Collection接口。這個接口中有如下兩個方法:
boolean add(E element); Iteratoriterator();
add方法用於向集合中添加元素,如果添加元素確實改變了集合就返回true,如果集合沒有發生變化就返回false。
iterator方法用於返回一個實現了Iterator接口的對象。可以使用這個迭代器對象依次訪問集合中的元素。
下面來介紹一下Java類庫中重要的迭代器。
Iterator迭代器接口有三個方法:
public interface Iterator{ E next(); boolean hasNext(); void remove(); }
Collectionc=...; Iterator it=c.iterator(); while(it.hasNext()) { String element=it.next(); do something with element }
for(String element : c) { do something with element }編譯器只是將這個for each循環翻譯為帶有迭代器的循環。
for each循環可以與任何實現了Iterable接口的對象一起工作,這個接口只包含一個方法:
public interface Iterable{ Iterator iterator(); }
需要注意的是,元素被訪問的順序取決於集合的類型。如果對ArrayList進行迭代,迭代器將會從索引0開始,每迭代一次,索引值加1。然而,如果訪問HashSet中的元素,每個元素將會按照某種隨機的次序出現。雖然可以確定在迭代過程中能夠遍歷所有的元素,但卻無法預知元素被訪問的次序。
Java集合類庫中的迭代器與其它類庫中的迭代器在概念上有著重要的區別。C++的迭代器是根據數組索引建模的。如果給定這樣一個迭代器,就可以查看指定位置上的元素,就像知道數組索引i就可以查看數組元素a[i]一樣。不需要查找元素,就可以將迭代器向前移動一個位置。這與不需要執行查找操作就可以通過i++將數組索引向前移動一樣。但是,Java迭代器不是這樣操作的。查找操作和位置變更是緊密相連的。查找一個元素的唯一方法是調用next,而在執行查找操作的同時,迭代器的位置隨之向前移動。
即,可以將迭代器看做一個位置,這個位置在兩個元素之間。而next操作會改變這個位置。但調用next時,迭代器就越過下一個元素,並返回剛剛越過的那個元素的引用。
Iterator接口的remove方法用於刪除上次調用next方法返回時的元素。也就是說,remove操作和next操作具有依賴性,如果沒有調用next方法而調用remove方法是非法的,會拋出一個IllegalStateException異常。下面是刪除集合中的第一個元素:
Iteratorit=c.iterator(); it.next(); it.remove();
it.remove(); it.remove();必須先調用next方法越過待刪除的元素:
it.remove(); it.next(); it.remove();有一個不怎麼恰當的比喻,可以將迭代器看做光標:
Collectionc=ArrayList<>(); c.add("a"); c.add("b"); c.add("c"); Iterator it=c.iterator();
| a b c
當調用next方法,光標就會後移,然後返回剛才越過的元素:
it.next();此時,迭代器的位置如下:
a | b c
並返回元素a。
如果要刪除元素,刪除的行為就像後退鍵(Backspace)一樣,刪除光標後面(以右為前)的元素:
it.remove();此時,迭代器的位置如下:
| b c
和後退鍵不同的是,如果迭代器的後面即使還有元素,沒有調用next方法也不能刪除。
由於Collection接口和Iterator接口都是泛型接口,可以編寫操作任何集合類型的實用方法。其實,Collection接口中有很多方法:
public interface java.util.Collectionextends java.lang.Iterable { public abstract int size(); public abstract boolean isEmpty(); public abstract boolean contains(java.lang.Object); public abstract java.util.Iterator iterator(); public abstract java.lang.Object[] toArray(); public abstract T[] toArray(T[]); public abstract boolean add(E); public abstract boolean remove(java.lang.Object); public abstract boolean containsAll(java.util.Collection); public abstract boolean addAll(java.util.Collection); public abstract boolean removeAll(java.util.Collection); public boolean removeIf(java.util.function.Predicate); public abstract boolean retainAll(java.util.Collection); public abstract void clear(); public abstract boolean equals(java.lang.Object); public abstract int hashCode(); public java.util.Spliterator spliterator(); public java.util.stream.Stream stream(); public java.util.stream.Stream parallelStream(); }
當然,如果實現Collection接口的每一個類都要實現這些例行方法將是一件很煩人的事。為了能夠讓實現者更容易地實現這個接口,Java類庫提供了一個AbstractCollection類,在這個類裡提供了一些例行方法,這樣,一個具體的集合類就可以擴展AbstractCollection類而不需要實現所有的例行方法了,並可以覆蓋裡面的方法。
下圖給出了Java類庫中的所有接口:
其中,Collection和Map是集合框架的兩個主要接口,所有的集合類都實現了這兩個接口中的一個。Iterator接口和ListIterator接口是迭代器接口,而ListIterator接口提供了更豐富的操作,這個接口會在List列表中介紹。RandomAccess接口是一個標簽接口,也就是說這個接口沒有任何方法,但是可以用這個接口標注某個類,然後檢查一個類是否支持隨機訪問。
在隨後的介紹中,會詳細介紹這些接口的方法和使用。
下面是Java類庫中的所有實現類:
其中紅顏色的是抽象類。可以明顯的分為兩個部分,一個集合(Collection),一個映射(Map)。
這些是常用的類,再加上一些專用的類,比如:EnumSet、LinkedHashSet、EnumMap、LinkedHashMap、IdentityHashMap和WeakHashMap,一共14類,它們的特點如下:
對於有序無序、元素可重復和元素不可重復,特點總結如下,注意,對於Map,考察的是鍵的值是否可重復:
在後序的分析中,會給出這些具體類和集合的介紹:
2、Java集合(二):List列表
3、Java集合(三):Set集合
4、Java集合(四):Queue隊列
5、Java集合(五):專用Set和專用Map
6、Java集合(六):Java集合框架
7、Java集合(七):Java集合中的算法與遺留類