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

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

其下實現類與相關的實現類子類數量繁多。咱們僅以最常使用的 List 接口的關係爲例,簡單的畫圖瞭解一下 Collection 接口 List 部分的關係圖。
java集合源碼分析(一):Collection 與 AbstractCollectionmysql

List集合的實現類關係圖

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

1、Iterable 接口
Iterable 是最頂層的接口,繼承這個接口的類能夠被迭代。
java集合源碼分析(一):Collection 與 AbstractCollection面試

Iterable 接口的方法

iterator():用於獲取一個迭代器。算法

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

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

spliterator():JDK8 新增。用於獲取一個可分割迭代器。默認實現返回一個IteratorSpliterator類。sql

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

2、Collection 接口
java集合源碼分析(一):Collection 與 AbstractCollection數組

Collection 接口的方法

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

能夠看到, 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 簡單的瞭解一下它:

要實現不可修改的集合,程序員只需擴展此類併爲iterator和size方法提供實現。(由iterator方法返回的迭代器必須實現hasNext和next 。)

要實現可修改的集合,程序員必須另外重寫此類的add方法(不然將拋出UnsupportedOperationException ),而且iterator方法返回的迭代器必須另外實現其remove方法。

根據Collection接口規範中的建議,程序員一般應提供一個void(無參數)和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;
}./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天

6.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) 這個方法裏涉及到了一個擴容的過程:

// 成員變量,容許數組理論容許的大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 位運算,擴大當前容量的一半+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;
}

可能有人會疑問,MAX_ARRAY_SIZE應該就是容許擴容的最大大小了,爲何還能夠擴容到Integer.MAX_VALUE?

實際上,根據 JavaDoc 的解釋:

Some VMs reserve some header words in an array.
Attempts to allocate larger arrays may result in OutOfMemoryError

一些 JVM 可能會用數組頭存放一些關於數組的數據,通常狀況下,最好不要直接能夠擴容到Integer.MAX_VALUE,所以擴容到Integer.MAX_VALUE-8就是理論上容許的最大值了,可是若是真的大到了這個地步,就只能特殊狀況特殊對待,試試看可不能夠擴容到Integer.MAX_VALUE,若是再大就要溢出了。

7.clear
迭代而且刪除所有元素。

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

8.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。
image
最新2020整理收集的一些高頻面試題(都整理成文檔),有不少乾貨,包含mysql,netty,spring,線程,spring cloud、jvm、源碼、算法等詳細講解,也有詳細的學習規劃圖,面試題整理等,須要獲取這些內容的朋友請加Q君樣:909038429/./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天

相關文章
相關標籤/搜索