Java集合(1)一 集合框架

knock_小新發表於2017-12-12

引言

集合在任何語言中都是比較重要的基礎知識,不同的集合在實現上採用了各種不同的資料結構,導致了各個集合的效能以及使用方式上存在很大差異,深入瞭解集合框架的整體結構以及各個集合類的實現原理,並靈活使用各個集合對編碼有很大幫助。 本系列文章從集合框架的整體設計到原始碼細節分析了java.util包下各個集合相關介面、抽象類以及各種常用的集合實現類,希望通過這個系列的文章對大家理解各個集合有一定幫助。 如未做特殊說明,本系列所有原始碼出自以下JDK環境:

java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

介面框架

一個好的框架在設計上都會考慮將介面與實現分離,java.util也不例外。要快速理解java.util,第一步就是從他的介面設計入手,從介面的整體設計可以快速明確這個框架的作用和功能。 Java集合(1)一 集合框架

*圖1 集合介面框架*
通過上面的介面框架圖可以看出:Collection<E>和Map<K,V>是java.util框架中的兩個根介面,代表了兩種不同的資料結構:集合和對映表。而List<E>、Set<E>則是繼承自Collection<E>下最核心的兩個介面,List<E>有序可重複並可以通過整數索引來訪問,Set<E>不包含重複元素。下面我們來分別來說下這些核心介面的基本功能。

Collection<E>

public interface Collection<E> extends Iterable<E>
複製程式碼

Collection<E>介面是集合的根介面,他代表了一組元素。但是Collection<E>並不關心這組元素是否重複,是否有序。他只提供操作對這組元素的基本操作方法,怎麼新增,怎麼刪除,怎麼迴圈。所有的實現類都必須提供這些方法,下面列出了Collection<E>介面的部分方法:

int size();
boolean contains(Object o);
//Returns an iterator over the elements in this collection.
Iterator<E> iterator();
//Returns an array containing all of the elements in this collection.
Object[] toArray();
//Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
void clear();
複製程式碼

在Collection<E>介面的方法中有幾個需要注意的地方

iterator()

Iterator<E> iterator();
複製程式碼

iterator方法返回一個實現了Iterator介面的物件,作用是依次訪問集合中的元素,Iterator<E>介面包含3個方法:

boolean hasNext();
E next();
void remove();
複製程式碼

通過多次呼叫next()方法可遍歷集合中的所有元素,需要注意的是需要在呼叫next()之前呼叫hasNext()方法,並在hasNext()返回true的時候才可以呼叫next(),例如:

private static void collectionIterator() {
    //不用關注Arrays.asList,只需要知道他能返回一個Collection<E>介面就行
    Collection<String> collection = Arrays.asList("Java", "C++", "Python");
    Iterator<String> iterator = collection.iterator();
    while (iterator.hasNext()) {
        String string = (String) iterator.next();
        System.out.println(string);
    }
}
//output:
//Java
//C++
//Python
複製程式碼

從JDK5開始使用“for each”這種更加方便的方式來遍歷集合,只要實現了Iterable介面,都可以使用“for each”來遍歷,效果和使用iterator一樣。Iterable介面只包含一個方法:

Iterator<E> iterator();
複製程式碼
private static void foreachCollectionIterator() {
	Collection<String> collection = Arrays.asList("Java", "C++", "Python");
	for (String string : collection) {
		System.out.println(string);
	}
}
//output:
//Java
//C++
//Python
複製程式碼

toArray( ) 以及 toArray(T[ ] a)

toArray和toArray(T[ ] a)返回的都是當前所有元素的陣列。 toArray返回的是一個Object[]陣列,型別不能改變。 toArray(T[ ] a)返回的是當前傳入的型別T的陣列,更方便使用者操作,比如需要獲取一個String型別的陣列:toArray(new String[0])。

List<E>

public interface List<E> extends Collection<E>
複製程式碼

List<E>介面最重要的特點在有序(ordered collection)這個關鍵字上面,實現這個介面的類可以通過整數索引來訪問元素。他可以包含重複的元素。 除了包含Collection<E>介面的所有方法外,還包括跟索引有關的部分方法:

E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
複製程式碼

其中需要引起注意的地方是ListIterator這個類: List<E>介面在Iterator迭代器的基礎上提供了另一個迭代器ListIterator,先來看看ListIterator<E>介面的定義:

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void remove();
    void set(E e);
    void add(E e);
}
複製程式碼

ListIterator<E>介面繼承自Iterator<E>介面,所以他們的差異在ListIterator<E>介面新增的功能上:

  • ListIterator<E>可以向後迭代previous()
  • ListIterator<E>可以獲取前後索引nextIndex()
  • ListIterator<E>可以新增新值add(E e)
  • ListIterator<E>可以設定新值set(E e)

Set<E>

public interface Set<E> extends Collection<E>
複製程式碼

Set<E>介面在方法簽名上與Collection<E>介面是一樣的,只不過在方法的說明上有更嚴格的定義,最重要的特點是他拒絕新增重複元素,不能通過整數索引來訪問。Set<E>的equals方法定義如果兩個集相等是他們包含相同的元素但順序不必相同。 至於為什麼要定義一個方法簽名完全重複的介面,我的理解是為了讓框架結構更加清晰,將集合從可以新增重複元素和不可以新增重複元素,可以通過整數索引訪問和不可以通過整數索引這幾點上區別開來,這樣當程式設計師需要實現自己的集合時能夠更準確的繼承相應介面。

Map<K,V>

public interface Map<K,V>
複製程式碼

API說明上關於Map<K,V>的說明非常精煉:從鍵對映到值的一個物件,鍵不能重複,每個鍵至多對映到一個值。 從鍵不能重複這個特點很容易想到通過Set<E>來實現鍵,他的介面方法Set<K> keySet()也證明了這點,下面選取了Map<K,V>介面中的一些典型方法:

int size();
boolean containsKey(Object key);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void clear();
//Returns a Set view of the keys contained in this map.
Set<K> keySet();
Returns a Collection view of the values contained in this map.
Collection<V> values();
//Returns a Set view of the mappings contained in this map.
Set<Map.Entry<K, V>> entrySet();
boolean equals(Object o);
int hashCode();
複製程式碼

java Set<K> keySet() 返回對映中包含的鍵集檢視,是一個Set<E>,說明了對映中鍵是不可重複的。 java Collection<V> values() 返回對映中包含的值得集合檢視,Collection<E>,說明了對映中值是可以重複的。 java Set<Map.Entry<K,V>> entrySet() 返回對映中包含的對映集合檢視,這個檢視是一個Set<E>,這是由他的鍵集不能重複的特點決定的。 entrySet()返回的是一個Map.Entry<K,V>型別的集,Map.Entry<K,V>介面定義了獲取鍵值、設定值的方法,定義如下:

interface Entry<K,V> {
    K getKey();
    V getValue();
    V setValue(V value);
    boolean equals(Object o);
    int hashCode();
}
複製程式碼

類框架

從一個框架的介面知道了這個框架的結構和功能,能夠用來做什麼。但具體怎麼使用,怎麼擴充套件,那就需要了解框架中實現這些介面的部分。 java.util提供了一系列抽象類,來實現上面的介面,這些抽象類中提供了大量基本的方法。如果需要實現自己的集合類,擴充套件這些抽象類比直接繼承介面方便的多。 Java集合(1)一 集合框架

*圖2 抽象類以及實現類框架*
除了圖中的抽象類和具體實現類,還有部分歷史版本遺留下來的類,包括Vetor,Stack,Hashtable,Properties等,在這裡就不做說明,重點關注圖中的類即可。

抽象類

需要關注幾個關鍵的抽象類包括AbstractCollection<E>;、AbstractMap<K,V>、AbstractList<E>和AbstractSet<E>,從命名可以看出他們分別實現了Collection<E>、Map<K,V>、List<E>和Set<E>介面。 各個集合的關鍵區別就在每個集合所使用的資料結構和演算法上,所以在抽象類層面都沒有涉及具體的資料結構和演算法,只對操作這些資料結構的方法做了基本實現。

AbstractCollection<E>

public abstract class AbstractCollection<E> implements Collection<E>
複製程式碼

AbstractCollection<E>基本實現了Collection<E>下的所有方法,除了以下幾個方法:

public abstract Iterator<E> iterator();

public abstract int size();

public boolean add(E e) {
    throw new UnsupportedOperationException();
}
複製程式碼

如果需要實現的是一個不可修改的集合,只需要實現iterator()和size()方法即可,如果需要實現一個可修改的集合,必須重寫add(E e)方法。 在AbstractCollection<E>已經實現的方法中可以發現,AbstractCollection<E>所實現的所有方法都是通過Iterator<E>來操作的。

public boolean contains(Object o) {
    //獲取迭代器
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}
複製程式碼

AbstractList<E>

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
複製程式碼

AbstractList<E>抽象類在AbstractCollection<E>抽象類的基礎上新增了專屬於List<E>介面的部分方法,但大部分方法都是空方法,沒有具體實現。

abstract public E get(int index);

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

public E remove(int index) {
    throw new UnsupportedOperationException();
}

public Iterator<E> iterator() {
    return new Itr();
}

public ListIterator<E> listIterator() {
    return listIterator(0);
}

public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);

    return new ListItr(index);
}
複製程式碼

沒有實現的原因在於AbstractList<E>是一個抽象類,他並沒有確定具體的資料結構,當在資料結構沒有確定的情況下,是直接使用整數索引的方式還是通過迭代器迴圈遍歷的方式來查詢具體的位置更方便是不確定的,所以在具體實現上都由他的子類來決定。 值得注意的是AbstractList<E>實現了Iterator以及ListIterator兩種型別的迭代器,很大程度上方便了子類的擴充套件:

private class Itr implements Iterator<E> {
//......
    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            //向後遍歷集合,通過get(i)獲取當前索引的元素,每次呼叫之後cursor = i + 1,get(i)為抽象方法
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            //通過AbstractList<E>類的remove方法來刪除元素,AbstractList<E>中remove(int index)是一個空方法,需要子類來實現
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
}
//......

private class ListItr extends Itr implements ListIterator<E> {
//......
    public E previous() {
        checkForComodification();
        try {
            int i = cursor - 1;
            //向前遍歷集合,通過get(i)獲取當前索引的元素,每次呼叫之前cursor - 1,get(i)為抽象方法
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
//......
}
複製程式碼

AbstractSet<E>

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>
複製程式碼

AbstractSet<E>抽象類在實現上非常簡單,只在AbstractCollection<E>抽象類的基礎上實現了equal 和 hashCode 方法,但具體的實現還是需要通過contain()方法來判斷,由於Set<E>介面型別不考慮元素的順序,所以只要兩個AbstractSet<E>包含相同元素就判斷為相等,不需要元素順序相同,而AbstractList<E>則需要順序也相同。

//AbstractSet<E> 中的 equals
public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    if (c.size() != size())
        return false;
    try {
        //containsAll在AbstractCollection<E>中已經實現,只要包含所有元素就可以
        return containsAll(c);
    } catch (ClassCastException unused)   {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

//AbstractCollection<E> 中的containsAll
public boolean containsAll(Collection<?> c) {
    for (Object e : c)
        if (!contains(e))
            return false;
    return true;
}

//AbstractList<E> 中的equals
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    //需要兩個集合中的元素以及元素順序都相同才返回true
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}
複製程式碼

AbstractMap<K,V>

public abstract class AbstractMap<K,V> implements Map<K,V>
複製程式碼

AbstractMap<K,V>抽象類中實現了除entrySet()方法外的基本所有方法,其中返回鍵集的Set<K> keySet()和返回值集的Collection<V> values()在實現上非常有趣,從返回值上看是建立了一個新的集合,但實際實現上是返回來一個實現Set<K>或Collection<V>的類物件,類物件的所有操作都是在原對映表的基礎上進行的,這種有趣的操作叫檢視,java.util框架中存在大量應用。 這裡使用檢視的好處在於抽象類中你不需要確定返回的Set<K>或Collection<V>的具體實現類是什麼,這樣就可以在抽象類中沒有決定使用哪種資料結構的時候最大化抽象類的功能,增加擴充套件的方便性。 keySet()的原始碼:

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new AbstractSet<K>() {
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    //獲取原對映表的迭代器來實現自己的迭代器
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public K next() {
                        return i.next().getKey();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                //直接操作原對映表的size()方法
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object k) {
                return AbstractMap.this.containsKey(k);
            }
        };
        keySet = ks;
    }
    return ks;
}
複製程式碼

總結

java.util這個框架的結構還是非常清晰的,從介面的分類,每個介面的抽象類實現,都很好的保證了框架的伸縮性,為後續的實現和自定義擴充套件提供了極大地方便。 除了上述的介面以及抽象類以外,java.util框架還提供了一些其他結構,在使用頻率上不是太高,比如Queue<E> ,Deque<E> 等。 在後面的章節中我們會詳細講解幾個關鍵集合的實現,從資料結構、演算法以及效能等各方面分析孰優孰劣。

相關文章