Java集合(一):Java集合概述
1 概述
Java提供了一個豐富的集合架構,這個集合架構套件含了許多介面、虛擬類和實作類別。這些介面和類提供了豐富的功能,能夠滿足基本的彙總需求。就是這個架構的整體結構圖:
可以看見,這個架構非常大,大到吃驚的地步。這個圖的左面部分是介面,右面部分是類,中間的線代表了右面的類實現了左面的哪些介面。比如,AbstractList類實現了List介面,那麼繼承自AbstractList類的子類都實現了這個介面。還有,如果一個類實現了一個介面,那麼這個類也實現了這個介面的所有父介面。比如,TreeSet類實現了Deque介面,那麼TreeSet類也實現了Queue介面、Collection介面和Iterable介面(介面Collection擴充了Iterable,這裡沒顯示出來)。
圖中紅色的類表示抽象類別,大部分抽象類別都以Abstract開頭,除了Dictionary。綠顏色的類表示遺留類,這些類在Java一開始的時候就已經存在了。
2 介面與實現分離
Java集合類庫中將介面(interface)與實現(implementation)分離。這裡以隊列(queue)為例。
隊列介面指出可以在隊列的尾部添加元素,在隊列的頭部刪除元素,並且可以尋找隊列中的元素個數。即隊列中的元素按照先進先出的元素使用隊列。
如果我們設計queue的介面,可能這樣設計:
interface Queue{ void add(E element); E remove(); int size();}這裡給出了三個基本的方法。這個介面沒有給出隊列應該是如何?的。通常,隊列的實現方式有兩種:一個是使用迴圈數組,一個是使用鏈表。
使用迴圈數組時需要指出對頭head和隊尾tail;使用鏈表時也需要給出頭和尾:
class CircularArrayQueue implements 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;}上面的只是我們自己實現的簡單的隊列,Java類庫中沒有這兩個類。當需要使用隊列時,可以使用LinkedList類,這個類實現了Queue介面。
當在程式中使用隊列時,一旦構建了集合就不需要知道究竟使用了哪種實現。因此,只有在構建集合對象時,使用具體的類才有意義。可以使用介面類型存放集合的引用:
Queue employee=new CircularArrayQueue<>(100);employee.add(new Employee("Harry"));利用這種方式,一旦改變了想法,可以輕鬆地使用另一種不同的實現。只需要對程式的一個地方做出修改,即調用構造器的地方。如果覺得LinkedListQueue是個更好的選擇,就可以這樣修改:
Queue employee=new LinkedListQueue<>();employee.add(new Employee("Harry"));介面本身沒有給出具體的實現方式,因此也不能給出哪種實現更符合實際應用。這樣,選擇哪種實現方式就需要由程式員選擇。Java類庫中實現了很多的類,每個類都有各自的特性以及適用情境,也許選擇了某個實現方式在某個特性上更加優秀,但也可能在另一個特性上付出了代價。如何平衡好各個效能的代價,需要類庫使用者自己把握。
在中,我們可以發現很多紅色的以Abstract開頭的類,這些類都是抽象類別,這些類是為類庫的實現者設計的,這些類中實現了一些基本的方法。如果想要實現自己的隊列類,你會發現擴充AbstractQueue類比實現Queue介面更方便。
3 Java類庫中的集合介面和迭代器介面
在Java類庫中,集合類的基本介面是Collection介面。這個介面中有如下兩個方法:
boolean add(E element);Iterator iterator();
當然,除了這兩個方法外還有其它的方法,會在後面介紹。
add方法用於向集合中添加元素,如果添加元素確實改變了集合就返回true,如果集合沒有發生變化就返回false。
iterator方法用於返回一個實現了Iterator介面的對象。可以使用這個迭代器對象依次訪問集合中的元素。
下面來介紹一下Java類庫中重要的迭代器。
Iterator迭代器介面有三個方法:
public interface Iterator{ E next(); boolean hasNext(); void remove();}通過反覆調用next方法,可以逐個訪問集合中的每個元素。但是如果達到了集合的末尾,next方法將拋出一個NoSuchElementException異常。因此,在調用next方法之前應該調用hasNext方法。如果迭代器對象還有多個供訪問的元素,這個方法就返回true。因此,可以使用下面的方法訪問集合中的所有元素:
Collection c=...;Iterator it=c.iterator();while(it.hasNext()){ String element=it.next(); do something with element}從Jave SE 5.0起,還可以使用for each逐一查看集合中的所有元素:
for(String element : c){ do something with element}編譯器只是將這個for each迴圈翻譯為帶有迭代器的迴圈。
for each迴圈可以與任何實現了Iterable介面的對象一起工作,這個介面只包含一個方法:
public interface Iterable{ Iterator iterator();}Collection介面擴充了Iterable介面,所以對於標準庫中的任何集合都可以使用for each迴圈。
需要注意的是,元素被訪問的順序取決於集合的類型。如果對ArrayList進行迭代,迭代器將會從索引0開始,每迭代一次,索引值加1。然而,如果訪問HashSet中的元素,每個元素將會按照某種隨機的次序出現。雖然可以確定在迭代過程中能夠遍曆所有的元素,但卻無法預知元素被訪問的次序。
Java集合類庫中的迭代器與其它類庫中的迭代器在概念上有著重要的區別。C++的迭代器是根據數組索引建模的。如果給定這樣一個迭代器,就可以查看指定位置上的元素,就像知道數組索引i就可以查看數組元素a[i]一樣。不需要尋找元素,就可以將迭代器向前移動一個位置。這與不需要執行尋找操作就可以通過i++將數組索引向前移動一樣。但是,Java迭代器不是這樣操作的。尋找操作和位置變更是緊密相連的。尋找一個元素的唯一方法是調用next,而在執行尋找操作的同時,迭代器的位置隨之向前移動。
即,可以將迭代器看做一個位置,這個位置在兩個元素之間。而next操作會改變這個位置。但調用next時,迭代器就越過下一個元素,並返回剛剛越過的那個元素的引用。
Iterator介面的remove方法用於刪除上次調用next方法返回時的元素。也就是說,remove操作和next操作具有依賴性,如果沒有調用next方法而調用remove方法是非法的,會拋出一個IllegalStateException異常。下面是刪除集合中的第一個元素:
Iterator it=c.iterator();it.next();it.remove();
如果刪除兩個相鄰的元素,下面的方法是錯誤的:
it.remove();it.remove();
必須先調用next方法越過待刪除的元素:
it.remove();it.next();it.remove();
有一個不怎麼恰當的比喻,可以將迭代器看做游標:
Collection c=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.Collection extends 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類而不需要實現所有的例行方法了,並可以覆蓋裡面的方法。
4 Java類庫中的介面
給出了Java類庫中的所有介面:
其中,Collection和Map是集合架構的兩個主要介面,所有的集合類都實現了這兩個介面中的一個。Iterator介面和ListIterator介面是迭代器介面,而ListIterator介面提供了更豐富的操作,這個介面會在List列表中介紹。RandomAccess介面是一個標籤介面,也就是說這個介面沒有任何方法,但是可以用這個介面標註某個類,然後檢查一個類是否支援隨機訪問。
在隨後的介紹中,會詳細介紹這些介面的方法和使用。
5 Java類庫中的類
下面是Java類庫中的所有實作類別:
其中紅顏色的是抽象類別。可以明顯的分為兩個部分,一個集合(Collection),一個映射(Map)。
這些是常用的類,再加上一些專用的類,比如:EnumSet、LinkedHashSet、EnumMap、LinkedHashMap、IdentityHashMap和WeakHashMap,一共14類,它們的特點如下:
Java庫中的具體集合
| 集合類型 |
描述 |
| ArrayList |
一種可以動態增長和伸縮的索引序列 |
| LinkedList |
一種可以在任何位置進行高效插入和刪除操作的有序序列 |
| ArrayDeque |
一種用迴圈數組實現的雙端隊列 |
| HashSet |
一種沒有重複元素的無序集合 |
| TreeSet |
一種有序集 |
| EnumSet |
一種包含枚舉類型值的集 |
| LinkedHashSet |
一種可以記住元素插入順序的集 |
| PriorityQueue |
一種允許高效刪除最小元素的集合 |
| HashMap |
一種儲存鍵/值關聯的資料結構 |
| TreeMap |
一種索引值有序排列的映射表 |
| EnumMap |
一種索引值屬於枚舉類型的映射表 |
| LinkedHashMap |
一種可以記住索引值項添加次序的映射表 |
| WeakHashMap |
一種其值無用武之地後可以被記憶體回收行程回收的映射表 |
| IdentityHashMap |
一種使用==而不是equals比較索引值的映射表 |
對於有序無序、元素可重複和元素不可重複,特點總結如下,注意,對於Map,考察的是鍵的值是否可重複:
6 更多
在後序的分析中,會給出這些具體類和集合的介紹:
2、Java集合(二):List列表
3、Java集合(三):Set集合
4、Java集合(四):Queue隊列
5、Java集合(五):專用Set和專用Map
6、Java集合(六):Java集合架構
7、Java集合(七):Java集合中的演算法與遺留類