电竞比分网-中国电竞赛事及体育赛事平台

分享

深入理解Java之集合框架

 jnstyle 2016-03-21

1. 概述

    Java集合框架由Java類庫的一系列接口、抽象類以及具體實現(xiàn)類組成。我們這里所說的集合就是把一組對象組織到一起,然后再根據(jù)不同的需求操縱這些數(shù)據(jù)。集合類型就是容納這些對象的一個容器。也就是說,最基本的集合特性就是把一組對象放一起集中管理。根據(jù)集合中是否允許有重復的對象、對象組織在一起是否按某種順序等標準來劃分的話,集合類型又可以細分為許多種不同的子類型。

    Java集合框架為我們提供了一組基本機制以及這些機制的參考實現(xiàn),其中基本的集合接口是Collection接口,其他相關(guān)的接口還有Iterator接口、RandomAccess接口等。這些集合框架中的接口定義了一個集合類型應(yīng)該實現(xiàn)的基本機制,Java類庫為我們提供了一些具體集合類型的參考實現(xiàn),根據(jù)對數(shù)據(jù)組織及使用的不同需求,只需要實現(xiàn)不同的接口即可。Java類庫還為我們提供了一些抽象類,提供了集合類型功能的部分實現(xiàn),我們也可以在這個基礎(chǔ)上去進一步實現(xiàn)自己的集合類型。

 

2. Collection接口

(1)迭代器

    我們先來看下這個接口的定義:

public interface Collection<E>
extends Iterable<E>

    首先,它使用了一個類型參數(shù),關(guān)于類型參數(shù)請參考 深入理解Java之泛型;其次,它實現(xiàn)了Iterable<E>接口,我們再來簡單看下Iterable<E>接口的定義:

1 public interface Iterable<T> {
2     Iterator<T>    iterator();
3 }

    我們可以看到這個接口只定義了一個方法,這個方法要求我們返回一個實現(xiàn)了Iterator<T>接口的對象,所以我們看下Iterator<T>的定義:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

    說到這里,我們簡單地說一下迭代器(Iterator)這個東西。上面我們一共提到了兩個和迭代器相關(guān)的接口:Iterable<E>接口和Iterator<E>接口,從字面意義上來看,前者的意思是“可迭代的”,后者的意思是“迭代器。所以我們可以這么理解這兩個接口:實現(xiàn)了Iterable<E>接口的類是可迭代的;實現(xiàn)了Iterator<E>接口的類是一個迭代器。

    迭代器就是一個我們用來遍歷集合中的對象的東西。也就是說,對于集合,我們不是像對原始類型數(shù)組那樣通過直接訪問元素來迭代,而是通過迭代器來遍歷對象。這么做的好處是將對于集合類型的遍歷行為與被遍歷的集合對象分離,這樣一來我們無需關(guān)心該集合類型的具體實現(xiàn)是怎樣的。只要獲取這個集合對象的迭代器, 便可以遍歷這個集合中的對象了。而像遍歷對象的順序這些細節(jié),全部由它的迭代器來處理?,F(xiàn)在我們來梳理一下前面提到的這些東西:首先,Collection接口實現(xiàn)了Iterable<E>接口,這意味著所有實現(xiàn)了Collection接口的具體集合類都是可迭代的。那么既然要迭代,我們就需要一個迭代器來遍歷相應(yīng)集合中的對象,所以Iterable<E>接口要求我們實現(xiàn)iterator方法,這個方法要返回一個迭代器對象。一個迭代器對象也就是實現(xiàn)了Iterator<E>接口的對象,這個接口要求我們實現(xiàn)hasNext()、next()、remove()這三個方法。其中hasNext方法判斷是否還有下一個元素(即是否遍歷完對象了),next方法會返回下一個元素(若沒有下一個元素了調(diào)用它會引起拋出一個NoSuchElementException異常),remove方法用于移除最近一次調(diào)用next方法返回的元素(若沒有調(diào)用next方法而直接調(diào)用remove方法會報錯)。我們可以想象在開始對集合進行迭代前,有個指針指向集合第一個元素的前面,第一次調(diào)用next方法后,這個指針會”掃過"第一個元素并返回它,調(diào)用hasNext方法就是看這個指針后面還有沒有元素了。也就是說這個指針始終指向剛遍歷過的元素和下一個待遍歷的元素之間。通常,迭代一個集合對象的代碼是這個樣子的:

Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
    String element = iter.next();
    do something with element;
}

   Java SE 5.0為我們提供一了一個以上代碼段的等價但是更加簡潔的版本:

for (String element : c) {
    do something with element;
}

    上面我們提到過Iterator接口的remove方法必須在next方法返回一個元素后才能調(diào)用,這對Java類庫中為我們提供的實現(xiàn)了Collection接口的類來說是這樣的。當然我們可以通過自己定義一個實現(xiàn)Collection接口的集合類來改變這一默認行為(除非有充足的理由,否則最好不要這樣做)。

 

(2)Collection接口

     我們先來看一下它的官方定義:

    The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set and List

    大概的意思就是:Collection接口是集合層級結(jié)構(gòu)的根接口。一個集合代表了一組對象,這組對象被稱為集合的元素。一些集合允許重復的元素而其他不允許;一些是有序的而一些是無序的。Java類庫中并未提供任何對這個接口的直接實現(xiàn),而是提供了對于它的更具體的子接口的實現(xiàn)(比如Set接口和List接口)。

    我們知道,接口是一組對需求的描述,那么讓我們看看Collection接口提出了哪些需求。Collection接口中定義了以下方法:

boolean    add(E e) //向集合中添加一個元素,若添加元素后集合發(fā)生了變化就返回true,若沒有發(fā)生變化,就返回false。(optional operation).
boolean    addAll(Collection<? extends E> c) //添加給定集合c中的所有元素到該集合中(optional operation).
void    clear()  //(optional operation).
boolean    contains(Object o) //判斷該集合中是否包含指定對象
boolean    containsAll(Collection<?> c)
boolean    equals(Object o)
int    hashCode()
boolean    isEmpty()
Iterator<E>    iterator()
boolean    remove(Object o) //移除給定對象的一個實例(有的具體集合類型允許重復元素) (optional operation).
boolean    removeAll(Collection<?> c) //(optional operation).
boolean    retainAll(Collection<?> c)   //僅保留給定集合c中的元素(optional operation).
int    size()
Object[]    toArray()
<T> T[]    toArray(T[] a)

 

     我們注意到有些方法后面注釋中標注了“optional operation”,意思是Collection接口的實現(xiàn)類究竟需不需要實現(xiàn)這個方法視具體情況而定。比如有些具體的集合類型不允許向其中添加對象那么就可以不實現(xiàn)add方法。

    我們來說一下后兩個方法,它們的功能都是都是返回這個集合的對象數(shù)組。第二個方法接收一個arrayToFill參數(shù),當這個參數(shù)數(shù)組足夠大時,就把集合中的元素都填入這個數(shù)組(多余空間填null);當arrayToFill不夠大時,就會創(chuàng)建一個大小與集合相同,類型與arrayToFill相同的數(shù)組,并填入集合元素。

    Collection接口的直接子接口主要有三個:List接口、Set接口和Queue接口。下面我們對它們進行逐一介紹。

 

(3)List接口

    我們同樣先看下它的官方定義:

    An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.Unlike sets, lists typically allow duplicate elements. More formally, lists typically allow pairs of elements e1 and e2 such that e1.equals(e2), and they typically allow multiple null elements if they allow null elements at all.

    大概意思是:List是一個有序的集合類型(也被稱作序列)。使用List接口可以精確控制每個元素被插入的位置,并且可以通過元素在列表中的索引來訪問它。列表允許重復的元素,并且在允許null元素的情況下也允許多個null元素。

    我們再來看下它定義了哪些方法:

ListIterator<E> listIterator();
void add(int i, E element);
E remove(int i);
E get(int i);
E set(int i, E element);
int indexOf(Object element);

 

    我們可以看到,列表支持對指定位置元素的讀寫與移除。我們注意到,上面有一個listIterator方法,它返回一個列表迭代器。我們來看一看ListIterator<E>接口都定義了哪些方法:

void    add(E e) //在當前位置添加一個元素
boolean    hasNext() //返回ture如果還有下個元素(在正向遍歷列表時使用)
boolean    hasPrevious() //反向遍歷列表時使用
E    next()  //返回下一個元素并將cursor(也就是我們上文提到的”指針“)前移一個位置
int    nextIndex() //返回下一次調(diào)用next方法將返回的元素的索引
E    previous()   //返回前一個元素并將cursor向前移動一個位置
int    previousIndex() //返回下一次調(diào)用previous方法將返回的元素的索引
void    remove() //從列表中移除最近一次調(diào)用next方法或previous方法返回的元素
void    set(E e) //用e替換最近依次調(diào)用next或previous方法返回的元素

 

    ListIterator<E>是Iterator<E>的子接口,因而它支持像雙向迭代這樣更加特殊化的操作。綜合以上,我們可以看到,List接口支持兩種訪問元素的方式:使用列表迭代器順序訪問或者使用get/set方法隨機訪問。

    Java類庫中常見的實現(xiàn)了List<E>接口的類有:ArrayList, LinkedList,Stack,Vector,AbstractList,AbstractSequentialList等等。

 

a. ArrayList<E>類

    ArrayList<E>是一個可動態(tài)調(diào)整大小的數(shù)組,允許null類型的元素。我們知道,Java中的數(shù)組大小在初始化時就必須確定下來,而且一旦確定就不能改變,這會使得在很多場景下不夠靈活。ArrayList<E>很好地幫我們解決了這個問題,當我們需要一個能根據(jù)包含元素的多少來動態(tài)收縮伸張的數(shù)組時,那么ArrayList<E>正是我們所需要的。

    我們先來看看這個類的常用方法:

boolean    add(E e) //添加一個元素到列表末尾
void    add(int index, E element) //添加一個元素到列表的指定位置
void    clear()
boolean    contains(Object o)
void    ensureCapacity(int minCapacity) //確保ArrayList至少能容納參數(shù)指定數(shù)目的對象,若有需要會增加ArrayList實例的容量。
E    get(int index) //返回指定位置的元素
int    indexOf(Object o)
boolean    isEmpty()
Iterator<E>    iterator()
ListIterator<E>    listIterator()
E    remove(int index)
boolean    remove(Object o)
E    set(int index, E element)
int    size()

     當我們插入了比較多的元素,導致ArrayList快要裝滿時,它會自動增長容量。而自動增長容量是通過創(chuàng)建一個新的容量更大的ArrayList對象,再把原來的所有元素復制過去實現(xiàn)的。若要想避免這種開銷,在知道大概會容納多少數(shù)據(jù)時,我們可以在構(gòu)造時指定好它的大小以盡量避免它自動增長的發(fā)生;我們也可以調(diào)用ensureCapacity方法來增加ArrayList對象的容量到我們指定的大小。ArrayList有以下三個構(gòu)造器:

ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity)

 

 

b. LinkedList類

    LinkedList類代表了一個雙向鏈表,允許null元素。這個類同ArrayList一樣,不是線程安全的。

    這個類中主要有以下的方法:

void addFirst(E element);
void addLast(E element);
E getFirst();
E getLast();
E removeFirst();
E removeLast();

 

    這些方法的含義正如它們的名字所示。LinkedList作為List接口的實現(xiàn)類,自然包含了List接口中定義的add等方法。LinkedList的add方法實現(xiàn)有以下兩種:

boolean    add(E e) //把元素e添加到鏈表末尾
void    add(int index, E element) //在指定索引處添加元素

 

    LinkedList的一個缺陷在于它不支持對元素的高效隨機訪問,要想隨機訪問其中的元素,需要逐個掃描直到遇到符合條件的元素。只有當我們需要減少在列表中間添加或刪除元素操作的代價時,可以考慮使用LinkedList。

 

(4)Set接口

    Set接口與List接口的重要區(qū)別就是它不支持重復的元素,之多可以包含一個null類型元素。Set接口定義的是數(shù)學意義上的“集合”概念。

    Set接口主要定義了以下方法:

boolean    add(E e)
void    clear()
boolean    contains(Object o)
boolean    isEmpty()
boolean  equals(Object obj)
Iterator<E>    iterator().
boolean    remove(Object o)
boolean    removeAll(Collection<?> c)
int    size()
Object[]    toArray()
<T> T[]    toArray(T[] a)

 

    Set接口并沒有顯式要求其中的元素是有序或是無序的,它有一個叫做SortedSet的子接口,這個接口可以用來實現(xiàn)對Set元素的排序,SortedSet還有叫做NavigableSet的子接口,這個接口定義的方法可以在有序Set中進行查找和遍歷。Java類庫中實現(xiàn)了Set接口的具體類主要有:AbstractSet,HashSet,TreeSet,EnumSet,LinkedHashSet等等。其中,HashSet與TreeSet都是AbstractSet的子類。那么,為什么Java類庫要提供AbstractSet這個抽象類呢?答案是為了讓我們在自定義實現(xiàn)Set接口的類時不必“從零開始”,AbstractSet這個抽象類已經(jīng)為我們實現(xiàn)了Set接口中的一些常規(guī)方法,而一些靈活性比較強的方法可以由我們自己來定義,我們只需要繼承AbstractSet這個抽象類即可。類似的抽象類還有很多,比如我們上面提到的實現(xiàn)了List接口的AbstractList抽象類就是LinkedList和ArrayList的父類。Java官方文檔中提到,HashSet和TreeSet分別基于HashMap和TreeMap實現(xiàn)(我們在后面會簡單介紹HashMap和TreeMap),他們的區(qū)別在于Set<E>接口是一個對象的集(數(shù)學意義上的”集合“),Map<K, V>是一個鍵值對的集合。而且由于它們分別是對Set<E>和Map<K, V>接口的實現(xiàn),相應(yīng)添加與刪除元素的方法也取決與具體接口的定義。

    

 (5)Queue接口

    Queue接口是對隊列這種數(shù)據(jù)結(jié)構(gòu)的抽象。一般的隊列實現(xiàn)允許我們高效的在隊尾添加元素,在隊列頭部刪除元素(First in, First out)。Queue<E>接口還有一個名為Deque的子接口,它允許我們高效的在隊頭或隊尾添加/刪除元素,實現(xiàn)了Deque<E>的接口的集合類即為雙端隊列的一種實現(xiàn)(比如LinkedList就實現(xiàn)了Deque接口)。Queue接口定義了以下方法:

boolean    add(E e) //添加一個元素到隊列中,若隊列已滿會拋出一個IllegalStateException異常
E    element() //獲取隊頭元素
boolean    offer(E e) //添加一個元素到隊列中,若隊列已滿返回false
E    peek()  //獲取隊頭元素,若隊列為空返回null
E    poll()  //返回并移除隊頭元素,若隊列為空返回null
E    remove() //返回并移除隊頭元素

 

    我們注意觀察下上面的方法:add與offer,element與peek,remove與poll看似是三對兒功能相同的方法。它們之間的重要區(qū)別在于前者若操作失敗會拋出一個異常,后者若操作失敗會從返回值體現(xiàn)出來(比如返回false或null),我們可以根據(jù)具體需求調(diào)用它們中的前者或后者。

    實現(xiàn)Queue接口的類主要有:AbstractQueue, ArrayDeque, LinkedList,PriorityQueue,DelayQueue等等。關(guān)于它們具體的介紹可參考官方文檔或相關(guān)的文章。

 

3. Map接口

 我們先來看下它的定義:
    An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.The Map interface provides three collection views, which allow a map's contents to be viewed as a set of keys, collection of values, or set of key-value mappings. The order of a map is defined as the order in which the iterators on the map's collection views return their elements. Some map implementations, like the TreeMap class, make specific guarantees as to their order; others, like the HashMap class, do not.

    大概意思是這樣的一個把鍵(key)映射到值(值)的對象被稱作一個Map(映射表)對象。映射表不能包含重復的鍵,每個鍵至多可以與一個值關(guān)聯(lián)。Map接口提供了三個集合視圖(關(guān)于集合視圖的概念我們下面會提到):鍵的集合視圖、值的集合視圖以及鍵值對的集合視圖。一個映射表的順序取決于它的集合視圖的迭代器返回元素的順序。一些Map接口的具體實現(xiàn)(比如TreeMap)保證元素有一定的順序,其它一些實現(xiàn)(比如HashMap)則不保證元素在其內(nèi)部有序。

    也就是說,Map接口定義了一個類似于“字典”的規(guī)范,讓我們能夠根據(jù)鍵快速檢索到它所關(guān)聯(lián)的值。我們先來看看Map接口定義了哪些方法:

void    clear()
boolean    containsKey(Object key) //判斷是否包含指定鍵
boolean    containsValue(Object value) //判斷是否包含指定值
boolean    isEmpty()
V    get(Object key)  //返回指定鍵映射的值
V    put(K key, V value)  //放入指定的鍵值對
V    remove(Object key)
int    size()

Set<Map.Entry<K,V>>    entrySet() 
Set<K>    keySet()
Collection<V>    values()

 

    后三個方法在我們下面介紹集合視圖時會具體講解。

    Map接口的具體實現(xiàn)類主要有:AbstractMap,EnumMap,HashMap,LinkedHashMap,TreeMap。HashTable。

 

(1)HashMap

    下面我們看一下HashMap的官方定義:

    HashMap<K, V>是基于哈希表這個數(shù)據(jù)結(jié)構(gòu)的Map接口具體實現(xiàn),允許null鍵和null值。這個類與HashTable近似等價,區(qū)別在于HashMap不是線程安全的并且允許null鍵和null值。由于基于哈希表實現(xiàn),所以HashMap內(nèi)部的元素是無序的。HashMap對與get與put操作的時間復雜度是常數(shù)級別的(在散列均勻的前提下)。對HashMap的集合視圖進行迭代所需時間與HashMap的capacity(bucket的數(shù)量)與HashMap的尺寸(鍵值對的數(shù)量)之和成正比。因此,若迭代操作的性能很重要,不要把初始capacity設(shè)的過高(不要把load factor設(shè)的過低)。

    有兩個因素會影響一個HashMap對象的性能:intial capacity(初始容量)和load factor(負載因子)。intial capacity就是HashMap對象剛創(chuàng)建時其內(nèi)部的哈希表的“桶”的數(shù)量(請參考哈希表的定義)。load factor等于maxSize / capacity,也就是HashMap所允許的最大鍵值對數(shù)與桶數(shù)的比值。增大load factor可以節(jié)省空間但查找一個元素的時間會增加,減小load factor會占用更多的存儲空間,但是get與put的操作會更快。當HashMap中的鍵值對數(shù)量超過了maxSize(即load factor與capacity的乘積),它會再散列,再散列會重建內(nèi)部數(shù)據(jù)結(jié)構(gòu),桶數(shù)(capacity)大約會增加到原來的兩倍。

    HashMap默認的load factor大小為0.75,這個數(shù)值在時間與空間上做了很好的權(quán)衡。當我們清楚自己將要大概存放多少數(shù)據(jù)時,也可以自定義load factor的大小。

    HashMap的構(gòu)造器如下:

HashMap()
HashMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)
HashMap(Map<? extends K,? extends V> m) //創(chuàng)建一個新的HashMap,用m的數(shù)據(jù)填充

 

    常用方法如下:

void    clear()
boolean    containsKey(Object key)
boolean    containsValue(Object value)
V    get(Object key)
V    put(K key, V value)
boolean    isEmpty()
V    remove(Object key)
int    size()

Collection<V>    values()
Set<Map.Entry<K,V>>    entrySet()
Set<K>    keySet()

 

   它們的功能都很直觀,具體的注意事項可以參考Java官方文檔,這里就不貼上來了。這里簡單地提一下WeakHashMap,它與HashMap的區(qū)別在于,存儲在其中的key是“弱引用”的,也就是說,當不再存在對WeakHashMap中的鍵的外部引用時,相應(yīng)的鍵值對就會被回收。關(guān)于WeakHashMap和其他類的具體使用方法及注意事項,大家可以參考官方文檔。下面我們來簡單地介紹下另一個Map接口的具體實現(xiàn)——TreeMap。

 

(2)TreeMap

    它的官方定義是這樣的:

    TreeMap<K, V>一個基于紅黑樹的Map接口實現(xiàn)。TreeMap中的元素的有序的,排序的依據(jù)是存儲在其中的鍵的natural ordering(自然序,也就是數(shù)字從小到大,字母的話按照字典序)或者根據(jù)在創(chuàng)建TreeMap時提供的Comparator對象,這取決于使用了哪個構(gòu)造器。TreeMap的containsKey, get, put和remove操作的時間復雜度均為log(n)。 

    TreeMap有以下構(gòu)造器:

TreeMap() //使用自然序?qū)ζ湓剡M行排序
TreeMap(Comparator<? super K> comparator) //使用一個比較器對其元素進行排序
TreeMap(Map<? extends K,? extends V> m) //構(gòu)造一個與映射表m含有相同元素的TreeMap,用自然序進行排列
TreeMap(SortedMap<K,? extends V> m) //構(gòu)造一個與有序映射表m含有想用元素及元素順序的TreeMap 

 

    它的常見方法如下:

Map.Entry<K,V>    ceilingEntry(K key) //返回一個最接近且大于等于指定key的鍵值對。
K    ceilingKey(K key)
void    clear()
Comparator<? super K>    comparator() //返回使用的比較器,若按自然序則返回null
boolean    containsKey(Object key)
boolean    containsValue(Object value)
NavigableSet<K>    descendingKeySet() //返回一個包含在TreeMap中的鍵的逆序的NavigableSet視圖
NavigableMap<K,V>    descendingMap()
Set<Map.Entry<K,V>>    entrySet()
Map.Entry<K,V>    firstEntry()  //返回鍵最小的鍵值對
K    firstKey()
Map.Entry<K,V>    floorEntry(K key) //返回一個最接近指定key且小于等于它的鍵對應(yīng)的鍵值對
K    floorKey(K key)
V    get(Object key)
Set<K>    keySet()
Map.Entry<K,V>    lastEntry()  //返回與最大的鍵相關(guān)聯(lián)的鍵值對
K    lastKey()

    建議大家先了解下紅黑樹這個數(shù)據(jù)結(jié)構(gòu)的原理及實現(xiàn)(可參考算法(第4版) (豆瓣)),然后再去看官方文檔中關(guān)于這個類的介紹,這樣學起來會事半功倍。

   

    最后再簡單地介紹下NavigableMap<K, V>這個接口:

    實現(xiàn)了這個接口的類支持一些navigation methods,比如lowerEntry(返回小于指定鍵的最大鍵所關(guān)聯(lián)的鍵值對),floorEntry(返回小于等于指定鍵的最大鍵所關(guān)聯(lián)的鍵值對),ceilingEntry(返回大于等于指定鍵的最小鍵所關(guān)聯(lián)的鍵值對)和higerEntry(返回大于指定鍵的最小鍵所關(guān)聯(lián)的鍵值對)。一個NavigableMap支持對其中存儲的鍵按鍵的遞增順序或遞減順序的遍歷或訪問。NavigableMap<K, V>接口還定義了firstEntry、pollFirstEntry、lastEntry和pollLastEntry等方法,以準確獲取指定位置的鍵值對。

     總的來說,NavigableMap<K, V>接口正如它的名字所示,支持我們在映射表中”自由的航行“,正向或者反向迭代其中的元素并獲取我們需要的指定位置的元素。TreeMap實現(xiàn)了這個接口。    

 

4. 視圖(View)與包裝器

    下面我們來解決一個上面遺留的問題,也就是介紹一下集合視圖的概念。Java中的集合視圖是用來查看集合中全部或部分數(shù)據(jù)的一個”窗口“,只不過通過視圖我們不僅能查看相應(yīng)集合中的元素,對視圖的操作還可能會影響到相應(yīng)的集合。通過使用視圖可以獲得其他的實現(xiàn)了Map接口或Collection接口的對象。比如我們上面提到的TreeMap和HashMap的keySet()方法就會返回一個相應(yīng)映射表對象的視圖。也就是說,keySet方法返回的視圖是一個實現(xiàn)了Set接口的對象,這個對象中又包含了一系列鍵對象。

(1)輕量級包裝器

    Arrays.asList會發(fā)揮一個包裝了Java數(shù)組的集合視圖(實現(xiàn)了List接口)。請看以下代碼:

1 public static void main(String[] args) {
2     String[] strings = {"first", "second", "third"};
3     List<String> stringList = Arrays.asList(strings);
4     String s1 = stringList.get(0);
5     System.out.println(s1);
6     stringList.add(0, "new first");
7 }

 

    以上代碼會編譯成功,但是在運行時會拋出一個UnsupportedOperationException異常,原因是第6行調(diào)用了改變列表大小的add方法。Arrays.asList方法返回的封裝了底層數(shù)組的集合視圖不支持對改變數(shù)組大小的方法(如add方法和remove方法)的調(diào)用(但是可以改變數(shù)組中的元素)。實際上,這個方法調(diào)用了以下方法:

Collections.nCopies(n, anObject);

 

    這個方法會返回一個實現(xiàn)了List接口的不可修改的對象。這個對象包含了n個元素(anObject)。

 

(2)子范圍

    我們可以為很多集合類型建立一個稱為子范圍(subrange)的集合視圖。例如以下代碼抽出group中的第10到19個元素(從0開始計數(shù))組成一個子范圍:

List subgroup = group.subList(10, 20); //group為一個實現(xiàn)了List接口的列表類型

 

    List接口所定義的操作都可以應(yīng)用于子范圍,包括哪些會改變列表大小的方法,比如以下方法會把subgroup列表清空,同時group中相應(yīng)的元素也會從列表中移除:

subgroup.clear();

 

    對于實現(xiàn)了SortedSet<E>接口的有序集或是實現(xiàn)了SortedMap<K, V>接口的有序映射表,我們也可以為他們創(chuàng)建子范圍。SortedSet接口定義了以下三個方法:

SortedSet<E> subSet(E from, E to); //返回
SortedSet<E> headSet(E to);
SortedSet<E> tailSet(E from);

    

    SortedMap也定義了類似的方法:

SortedMap<K, V> subMap(K from, K to);
SortedMap<K, V> headMap(K to);
SortedMap<K, V> tailMap(K from);

 

(3)不可修改的視圖

    Collections類中的一些方法可以返回不可修改視圖(unmodifiable views):

Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap

 

 

(4)同步視圖

    若集合可能被多個線程訪問,那么我們就需要確保集合中的數(shù)據(jù)不會被以外破壞。Java類庫的設(shè)計者使用視圖機制來確保常規(guī)集合的線程安全。比如,我們可以調(diào)用以下方法將任意一個實現(xiàn)了Map接口的集合變?yōu)榫€程安全的:

Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());

 

    

(5)被檢驗視圖

    我們先看一下這段代碼:

1 ArrayList<String> strings = new ArrayList<String>();
2 ArrayList rawList = strings;
3 rawList.add(new Date());

 

    在以上代碼的第二行,我們把泛型數(shù)組賦值給了一個原始類型數(shù)組,這通常只會產(chǎn)生一個警告。而第三行我們往rawList中添加一個Date對象時,并不會產(chǎn)生任何錯誤。因為rawList內(nèi)部存儲的實際上是Object對象,而把任何對象都可以轉(zhuǎn)換為Object對象。那么我們怎么避免這一問題呢,請看以下代碼:

1 ArrayList<String> strings = new ArrayList<String>();
2 List<String> safeStrings = Collections.checkedList(strings, String.class);
3 ArrayList rawList = safeStrings;
4 rawList.add(new Date()); //Checked list throws a ClassCastException

 

    在第2行,我們通過包裝strings得到一個被檢驗視圖safeStrings。這樣在第4行嘗試添加非String對象時,便會拋出一個ClassCastException異常。

 

(6)集合視圖的本質(zhì)

      集合視圖本身不包含任何數(shù)據(jù),它只是對相應(yīng)接口的包裝。集合視圖所支持的所有操作都是通過訪問它所關(guān)聯(lián)的集合類實例來實現(xiàn)的。我們來看看HashMap的keySet方法的源碼:

 1  public Set<K> keySet() {
 2         Set<K> ks;
 3         return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
 4     }
 5 
 6     final class KeySet extends AbstractSet<K> {
 7         public final int size()                 { return size; }
 8         public final void clear()               { HashMap.this.clear(); }
 9         public final Iterator<K> iterator()     { return new KeyIterator(); }
10         public final boolean contains(Object o) { return containsKey(o); }
11         public final boolean remove(Object key) {
12             return removeNode(hash(key), key, null, false, true) != null;
13         }
14         public final Spliterator<K> spliterator() {
15             return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
16         }
17         public final void forEach(Consumer<? super K> action) {
18             Node<K,V>[] tab;
19             if (action == null)
20                 throw new NullPointerException();
21             if (size > 0 && (tab = table) != null) {
22                 int mc = modCount;
23                 for (int i = 0; i < tab.length; ++i) {
24                     for (Node<K,V> e = tab[i]; e != null; e = e.next)
25                         action.accept(e.key);
26                 }
27                 if (modCount != mc)
28                     throw new ConcurrentModificationException();
29             }
30         }
31     }

 

    我們可以看到,實際上keySet()方法返回一個內(nèi)部final類KeySet的實例。我們可以看到KeySet類本身沒有任何實例變量。我們再看第7行KeySet類定義的size()實例方法,它的實現(xiàn)就是通過直接返回HashMap的實例變量size。還有第8行的clear方法,實際上調(diào)用的就是HashMap對象的clear方法。

    keySet方法能夠讓你直接訪問到Map的鍵集,而不需要復制數(shù)據(jù)或者創(chuàng)建一個新的數(shù)據(jù)結(jié)構(gòu),這樣做往往比復制數(shù)據(jù)到一個新的數(shù)據(jù)結(jié)構(gòu)更加高效。考慮這樣一個場景:你需要把一個之前創(chuàng)建的數(shù)組傳遞給一個接收List參數(shù)的方法,那么你可以使用Arrays.asList方法返回一個包裝了數(shù)組的視圖(這需要的空間復雜度是常數(shù)級別的),而不用創(chuàng)建一個新的ArrayList再把原數(shù)組中的數(shù)據(jù)復制過去。    

 

    

5. Collections類

      我們要注意到Collections類與Collection接口的區(qū)別:Collection是一個接口,而Collections是一個類(可以看做一個靜態(tài)方法庫)。下面我們看一下官方文檔對Collections的描述:

      Collections類包含了大量用于操作或返回集合的靜態(tài)方法。它包含操作集合的多態(tài)算法,還有包裝集合的包裝器方法等等。這個類中的所有方法在集合或類對象為空時均會拋出一個NullPointerException。關(guān)于Collections類中的常用方法,我們上面已經(jīng)做了一些介紹,更加詳細的列表大家可以參考Java官方文檔。

 

 

6.總結(jié)

     關(guān)于Java集合框架,我們首先應(yīng)該把握住幾個核心的接口,請看下圖:

   

    我們還要了解到這些接口描述了一組什么樣的機制(哪些是optional operation),然后以此作為出發(fā)點,去了解具體哪些類實現(xiàn)了哪些機制。像這樣自頂向下的學習,我們很快就能掌握常見集合類的用法。對于一些我們平常經(jīng)常使用的類,我們還可以閱讀一下它的源碼,了解它的實現(xiàn)細節(jié),這樣我們以后使用起來會更加得心應(yīng)手。不過閱讀一些集合類(比如TreeMap、HashMap)的源碼需要我們具備一定的數(shù)據(jù)結(jié)構(gòu)與算法的基礎(chǔ)知識,這方面推薦閱讀 算法(第4版) (豆瓣)。

 

6. 參考資料

    《Java核心技術(shù)(卷一)》

   What is a view of a collection?

    Java SE 7 Docs

 

PS: 以上是我學習Java集合框架的總結(jié),許多方面的認識還很不到位,希望大家不吝賜教 : )

    

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多