java集合源碼分析(一):Collection 與 AbstractCollection

概述

咱們知道,java 中容器分爲 Map 集合和 Collection 集合,其中 Collection 中的又分爲 Queue,List,Set 三大子接口。java

其中, List 應該是平常跟咱們打交道最頻繁的接口了,按照 JavaDoc 的說明,List 是一種:編程

有序集合(也稱爲序列)。此接口的用戶能夠精確控制列表中每一個元素的插入位置。用戶能夠經過其整數索引(在列表中的位置)訪問元素,並在列表中搜索元素。segmentfault

咱們以 List 下 Vector,ArrayList,LinkedList 三大實現爲主,下面是他們之間的一個關係圖。其中,紅色表示抽象類,藍色表示接口。數組

List集合的實現類關係圖

根據上圖的類關係圖,咱們研究一下源碼中,類與類之間的關係,方法是如何從抽象到具體的。app

1、Iterable 接口

Iterable 是最頂層的接口,繼承這個接口的類能夠被迭代。函數式編程

Iterable 接口的方法

  • iterator():用於獲取一個迭代器。函數

  • forEach() :JDK8 新增。一個基於函數式接口實現的新迭代方法。ui

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • spliterator():JDK8 新增。用於獲取一個可分割迭代器。默認實現返回一個IteratorSpliterator類。this

    這個跟迭代器相似,可是是用於並行迭代的,關於具體的狀況能夠參考一下掘金的一個討論:Java8裏面的java.util.Spliterator接口有什麼用?設計

2、Collection 接口

Collection 接口的方法

Collection 是集合容器的頂級接口,他繼承了 Iterable 接口,即凡是 Collection 的實現類均可以迭代,List 也是 Collection 的子接口,所以也擁有此特性。

能夠看到, Collection 接口提供了十九個抽象方法,這些方法的命名都很直觀的反應的這些方法的功能。經過這些方法規定了 Collection的實現類的一些基本特性:可迭代,可轉化爲數組,可對節點進行添加刪除,集合間能夠合併或者互相過濾,可使用 Stream 進行流式處理。

1.抽象方法

咱們能夠根據功能簡單的分類介紹一下 Collection 接口提供的方法。

判斷類:

  • isEmpty():判斷集合是否不含有任何元素;
  • contains():判斷集合中是否含有至少一個對應元素;
  • containsAll():判斷集合中是否含另外一個集合的全部元素;

操做類:

  • add():讓集合包含此元素。若是由於除了已經包含了此元素之外的任何狀況而不能添加,則必須拋出異常;
  • addAll():將指定集合中的全部元素添加到本集合;
  • remove():從集合移除指定元素;
  • removeAll():刪除也包含在指定集合中的全部此集合的元素;
  • retainAll:今後集合中刪除全部未包含在指定集合中的元素;
  • clear():從集合中刪除全部元素;

輔助類:

  • size():獲取集合的長度。若是長度超過 Integer.MAX_VALU 就返回 Integer.MAX_VALU;

  • iterator():獲取集合的迭代器;

  • toArray():返回一個包含此集合中全部元素的新數組實例。由於是新實例,因此對原數組的操做不會影響新數組,反之亦然;

    它有一多態方法參數爲T[],此時調用 toArray()會將內部數組中的元素所有放入指定數組,若是結束後指定數組還有剩餘空間,那剩餘空間都放入null。

2.JDK8 新增抽象方法

此外,在 JDK8 中新增了四個抽象方法,他們都提供了默認實現:

  • removeIf:至關於一個filter(),根據傳入的函數接口的匿名實現類方法來判斷是否要刪除集合中的某些元素;
  • stream():JDK8 新特性中流式編程的靈魂方法,能夠將集合轉爲 Stream 流式進行遍歷,配合 Lambda 實現函數式編程;
  • parallelStream():同 stream() ,可是是生成並行流;
  • spliterator():重寫了 Iterable 接口的 iterator()方法。

3.equals 和 hashCode

值得一提的是 Collection 還重寫了 Object 的 equals()hashCode() 方法(或者說變成了抽象方法?),這樣實現 Collection 的類就必須從新實現 equals()hashCode() 方法

3、AbstractCollection 抽象類

AbstractCollection 是一個抽象類,他實現了 Collection 接口的一些基本方法。JavaDoc 也是如此描述的:

此類提供了Collection接口的基本實現,以最大程度地減小實現此接口所需的工做。

經過類的關係圖,AbstractCollection 下面還有一個子抽象類 AbstractList ,進一步提供了對 List 接口的實現。 咱們不難發現,這正是模板方法模式在 JDK 中的一種運用。

0.不支持的實現

在這以前,須要注意的是,AbstractCollection 中有一些比較特別的寫法,即實現了方法,可是默認一調用馬上就拋出 UnsupportedOperationException異常:

public boolean add(E e) {
    throw new UnsupportedOperationException();
}

若是想要使用這個方法,就必須本身去重寫他。這個寫法讓我糾結了好久,網上找了找也沒找到一個具體的說法。

參考 JDK8 新增的接口方法默認實現這個特性,我大膽猜想,這應該是針對一些實現 Collection 接口,可是又不想要實現 add(E e)方法的類準備的。在 JDK8 以前,接口沒有默認實現,若是抽象類還不提供一個實現,那麼不管實現類是否須要這個方法,那麼他都必定要實現這個方法,這明顯不太符合咱們設計的初衷。

1.isEmpty

很是簡短的方法,經過判斷容器 size 是否爲0判斷集合是否爲空。

public boolean isEmpty() {
    return size() == 0;
}

2.contains/containsAll

判斷元素是否存在。

public boolean contains(Object o) {
    Iterator<E> it = iterator();
    // 若是要查找的元素是null
    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;
}

containsAll()就是在contains()基礎上進行了遍歷判斷。

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

3.addAll

addAll()方法就是在 for 循環裏頭調用 add()

public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    for (E e : c)
        if (add(e))
            modified = true;
    return modified;
}

4.remove/removeAll

remove()這個方法與 contains()邏輯基本同樣,由於作了null判斷,因此List是默認支持傳入null的

public boolean remove(Object o) {
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext()) {
            if (it.next()==null) {
                it.remove();
                return true;
            }
        }
    } else {
        while (it.hasNext()) {
            if (o.equals(it.next())) {
                it.remove();
                return true;
            }
        }
    }
    return false;
}

5.removeAll/retainAll

removeAll()retainAll()的邏輯基本一致,都是經過 contains()方法判斷元素在集合中是否存在,而後選擇保存或者刪除。因爲 contains()方法只看是否存在,而不在乎有幾個,因此若是目標元素有多個,會都刪除或者保留。

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    boolean modified = false;
    Iterator<?> it = iterator();
    while (it.hasNext()) {
        if (c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    boolean modified = false;
    Iterator<E> it = iterator();
    while (it.hasNext()) {
        if (!c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}

5.toArray(擴容)

用於將集合轉數組。有兩個實現。通常經常使用的是無參的那個。

public Object[] toArray() {
    // 建立一個和List相同長度的數字
    Object[] r = new Object[size()];
    Iterator<E> it = iterator();
    for (int i = 0; i < r.length; i++) {
        // 若是數組長度大於集合長度
        if (! it.hasNext())
            // 用Arrays.copyOf把剩下的位置用null填充
            return Arrays.copyOf(r, i);
        r[i] = it.next();
    }
    // 若是數組長度反而小於集合長度,就擴容數組而且重複上述過程
    return it.hasNext() ? finishToArray(r, it) : r;
}

其中,在 finishToArray(r, it) 這個方法裏涉及到了一個擴容的過程:

// 位運算,擴大當前容量的一半+1
int newCap = cap + (cap >> 1) + 1;
// 若是擴容後的大小比MAX_ARRAY_SIZE還大
if (newCap - MAX_ARRAY_SIZE > 0)
    // 使用原容量+1,去判斷要直接擴容到MAX_ARRAY_SIZE,Integer.MAX_VALUE仍是直接拋OutOfMemoryError異常
    newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);

這裏的 MAX_ARRAY_SIZE 是一個常量:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

這裏又經過hugeCapacity()方法進行了大小的限制:

private static int hugeCapacity(int minCapacity) {
    // 若是已經大到溢出就拋異常
    if (minCapacity < 0)
        throw new OutOfMemoryError
        ("Required array size too large");
    // 容量+1是否仍是大於容許的數組最大大小
    return (minCapacity > MAX_ARRAY_SIZE) ?
        // 若是是,就把容量直接擴大到Integer.MAX_VALUE
        Integer.MAX_VALUE :
    // 不然就直接擴容到運行的數組最大大小
    MAX_ARRAY_SIZE;
}

6.clear

迭代而且刪除所有元素。

Iterator<E> it = iterator();
while (it.hasNext()) {
    it.next();
    it.remove();
}

7.toString

AbstractCollection 重寫了 toString 方法,這也是爲何調用集合的toStirng() 不是像數組那樣打印一個內存地址的緣由。

public String toString() {
    Iterator<E> it = iterator();
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

4、總結

Collection

Collection 接口類是 List ,Queue,Set 三大子接口的父接口,他繼承了 Iterable 接口,於是全部 Collection 的實現類均可以迭代。

Collection 中提供了規定了實現類應該實現的大部分增刪方法,可是並無規定關於如何使用下標進行操做的方法。

值得注意的是,他重規定了 equlas()hashCode()的方法,所以 Collection 的實現類的這兩個方法再也不跟 Object 類同樣了。

AbstractCollection

AbstractCollection 是實現 Collection 接口的一個抽象類,JDK 在這裏使用了模板方法模式,Collection 的實現類能夠經過繼承 AbstractCollection 得到絕大部分實現好的方法。

在 AbstractCollection 中,爲add()抽象方法提供了不支持的實現:即實現了方法,可是調用卻會拋出 UnsupportedOperationException。根據推測,這跟 JDK8 接口默認實現的特性同樣,是爲了讓子類能夠有選擇性的去實現接口的抽象方法,沒必要即便不須要該方法,也必須提供一個無心義的空實現。

AbstractCollection 提供了對添加複數節點,替換、刪除的單數和複數節點的方法實現,在這些實現裏,由於作了null判斷,所以是默認是支持傳入的元素爲null,或者集合中含有爲null的元素,可是不容許傳入的集合爲null。

AbstractCollection 在集合轉數組的 toArrays() 中提供了關於擴容的初步實現:通常狀況下新容量=舊容量 + (舊容量/2 + 1),若是新容量大於 MAX_ARRAY_SIZE,就會使用 舊容量+1去作判斷,若是已經溢出則拋OOM溢出,大於 MAX_ARRAY_SIZE 就使用 Integer.MAX_VALUE 做爲新容量,不然就使用 MAX_ARRY_SIZE。

相關文章
相關標籤/搜索