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。

相关文章
相关标签/搜索