JDK源码阅读——集合框架——AbstractCollection

AbstractCollection提供了几个通用的Collection的具体实现方法,如果想实现简单Collection接口的话,你的实现类可以选择继承这个类,只需要重写或者是实现size()iterator()方法,但是,如果你想实现一个支持增删集合内元素的集合的话,你还需要自己重写add()和remove().因为AbstractCollection不支持add()和remove(),如果直接调用,会抛出UnsupportOperationException.

首先,我们先来看定义

public abstract class AbstractCollection<E> implements Collection<E> 

特点:1 抽象类 2 实现了Collection

看他的方法和属性。

默认构造方法,用于子类构造函数调用

    protected AbstractCollection() {
    }

两个抽象方法,自己留着实现

    public abstract Iterator<E> iterator();

    public abstract int size();

判断集合内有没有元素

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

不过这个size()是由子类自己实现的,返回值不一定准确代表集合内到底有多少个元素。如果子类实现size()的时候直接返回个0,那么无论集合内有没有元素,有多少元素,isEmpty都会返回true。除非子类重写isEmpty()。

判断集合内是否包含某个元素

    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;
    }

代码逻辑很简单。首先先看参数o是不是null,如果是null的话,迭代遍历集合,如果某个元素为null,说明集合包含null。如果o不为null,则使用参数类的equals()方法与迭代器遍历出的每一个元素进行对比,匹配成功,则说明包含此元素

集合转Object数组

    public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

看这个方法的时候,我需要说明一点,在集合转数组的过程中,如果并发量足够大,而且子类支持在遍历期间对集合进行增删操作的情况下,这个方法返回的数组内拥有的元素数量,和集合本身拥有的元素数量,可能会出现不一致。

来看具体的逻辑。首先初始化一个长度为size()的Object数组。然后根据数组的下标从0开始为数组赋值,同时使用迭代器遍历集合。我们先看if条件不成立的时候,会顺序的将集合内的元素赋值给数组,这点非常好理解。主要看if条件成立的时候,到底是个什么样的逻辑。

不过,为了加深理解,我们首先来考虑几种情况。

第一种:集合不支持在(迭代器)遍历期间并发的对集合做增删操作。

这种情况下,if条件永远不会成立。因为数组的长度就是集合的size(),迭代器每一次指向的元素位置和数组循环遍历的元素位置是一样的。而且,在return的时候又一次判断了集合内还有没有元素,当然,这种情况下是肯定没有的。直接返回Object数组r。

第二种:集合支持在(迭代器)遍历期间并发的对集合做增删操作。

我们模拟这样一种场景:线程A此时拿到集合a的对象,对其进行toArray()操作,此时集合a内的元素数量为10。那么toArray()方法内,此时数组r的长度就为10,然后开始for循环。在这个过程中由于某种原因,线程B在for循环没有执行完的时候(假设此时for循环内i为5)抢占了对a的使用权,并且删除了集合内的两个元素,此时集合内元素数量为8。删除完成后,线程B释放掉集合a对象,for循环继续执行。当i为8时,迭代器此时指向集合内的第8个元素,也是集合内最后一个元素,此时,if条件成立,返回值为一个长度为8的Object数组。数组内的元素为集合内没有被线程B删除的那8个元素。

然后,我们看第三种场景,那就是线程B不是删除元素,而是增加了2个元素,其余场景一样。我们继续推导结果。if条件是肯定不会成立的,return的时候会去调用finishToArray()。再看这个方法之前,我们先看一下现在的变量结构与值

数组r:长度为10,里面的元素为集合内的10个元素(不考虑排序的情况下,就是没有增加前的那10个元素)。

迭代器对象it。此时指向集合内的第10个元素。

集合:此时拥有12个元素。

我们此时看finishToArray()方法

    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;
                // overflow-conscious code
                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

注意,这个方法是私有的,说明,这个方法只能由AbstractCollection自己用,他儿子都不能用。事实上,用到这个方法的地方只有两处,这也提醒我们,尽量将重复代码进行抽取,写成方法。

代码逻辑:首先使用变量i记录数组r的长度。之后进入while循环,条件就是判断迭代器是否还有下一个元素。循环体内再次使用一个变量cap记录本次循环中数组r的长度。如果i和cap相等,说明数组长度不够了,需要扩容了,具体的扩容操作为重新分配一个新数组,新数组的长度为r的长度的1.5倍+1。(cap >> 1 是位运算,相当于cap/2)同时将数组r的所有元素拷贝到新数组中。

如果i和cap不相等,那么就将迭代器指向的下一个元素赋值给数组第i个位置,并且对i进行自增操作。

在扩容过程中涉及到一个成员变量和一个方法,先看变量

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

这是数组长度所能允许的最大值。

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError
                ("Required array size too large");
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

方法逻辑很简单,就不解释了。

对于返回值的处理也很有意思,同样再次判断i的值是否与r相等。如果相等,说明数组内的元素数量恰好就是数组的长度,所以直接返回r就可以。如果不相等,说明新分配的数组长度超出了元素的总数,需要去除掉多分配的数组空间。

拿我们刚才的例子来说,一开始的时候i为10,迭代器中还有两个元素(或者说迭代器指向了第10个元素,后面还有11,12)开始循环的时候,if条件成立,需要对数组进行扩容,扩容后的新数组长度为16。再将11、12号元素放入新数组后,发现数组r内有空余的位置,也就是i不等于r.length。对r进行去空操作,也就是复制一个长度为i的新数组当做返回值。

视线再次回到toArray()方法,最后一种场景,返回值为长度为12的一个Object数组。

这就是线程不安全的表现之一。

集合转泛型数组

    public <T> T[] toArray(T[] a) {
        // Estimate size of array; be prepared to see more or fewer elements
        int size = size();
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                  .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();

        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { // fewer elements than expected
                if (a == r) {
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    return Arrays.copyOf(r, i);
                } else {
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            r[i] = (T)it.next();
        }
        // more elements than expected
        return it.hasNext() ? finishToArray(r, it) : r;
    }

逻辑和toArray()差不多,也会出现集合并发增删元素的问题。

首先使用一个变量size记录进入此方法时集合内的元素数量,然后开始分配数组。分配数据的时候又使用了最好玩的三目表达式。首先看参数数组a的长度是不是能够容纳集合内的所有元素,如果不够,就需要新分配一个长度为集合内元素数量的新数组。

根据集合内元素的数量,参数数组a的长度,以及集合是否允许在迭代器遍历期间进行增删操作,我们一共模拟以下几种场景。

第一种:集合不允许在迭代器遍历期间进行增删,集合内中元素的数量大于等于参数数组a的长度。

我们设定集合内有10个元素,即size()为10。数组的长度为5。

那么在循环之前,r为一个长度为10的泛型类型数组。

那么在循环中,if条件永远不会成立,数组r中存储的元素全部为集合内的元素,程序的返回值也是r,即一个包含集合内所有元素的,长度为集合元素数量的泛型类型数组。

第二种:集合不允许在迭代器遍历期间进行增删,集合内元素的数量小于参数数组a的长度。

让然设定集合内有10个元素,即size()为10,数组的长度此时设定为15。

r则为一个长度为15的泛型类型数组。

循环照常进行,当循环变量为10时,if条件成立,进入分支判断后,显然,a不等于r(因为此时r中已经存储了10个来自集合的元素),且a的长度(15)大于i(10),所以,会执行System.arraycopy()方法。我们简单的来看一下这个方法

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

这个方法不是由java实现的(具体请查阅native关键字相关知识),作用就是将数组src从srcPos开始,复制length个元素到数组dest中,放入的位置是数组dest的第destPos开始。

回到我们的分析,那么此时数组r中的i(10)个元素就会顺序的被复制到a中,并且下标不变。然后需要判断a的长度是否大于i,如果大于i,则剩下的空间用null来填充。

还记得Collection接口中对toArray(T[] a)的说明吗?如果期望的数组长度大于集合内的元素数量,那么多出来的数组空间全部用null来填充。这个方法就是体现。

第三种:集合允许在迭代器遍历期间进行增删,集合内元素的数量大于参数数组a的长度。

假设集合内有10个元素,参数数组a的长度为5。数组r为一个长度为10的数组。

假设在for循环执行到i为7的时候,集合删除了三个元素。那么函数的返回值就是拷贝r的前7个元素,组成一个新数组。

第四种,如果集合增加了元素,那么将会执行finishToArray()方法,最终返回一个包含集合所有元素的数组(包括新增加的元素)。

增加元素的方法

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

由此可见,AbstractCollection是不支持add操作的。子类如果需要此操作,需要自己重写这个方法。addAll也一样。

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

移除单个元素

    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;
    }

使用迭代器进行遍历。分为两种情况,一是参数o为null,遍历中查找就集合中是否有null,有的话就移除,返回true。二是参数不为null,遍历查找是否有此元素(使用equals判断),有的话移除,返回真。都没有的情况下,返回false。

判断一个集合是否是当前集合的子集

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

没啥技术含量,循环调用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;
    }

首先呢,参数c不能为null,否则会报NPE。然后遍历调用参数C的contains方法,为true时就在本集合内移除。

移除差集

和removeAll逻辑差不多

    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;
    }

清空集合

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

toString

    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(' ');
        }
    }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值