java.util.AbstractCollection源码解析

源码摘自jdk1.6,水平有限,难免有错,欢迎大家指出,仅当笔记留存。


AbstractCollection类直接实现Collection接口,他作为集合的顶级父类,为下面的集合类例如AbstractList、AbstractSet等实现了许多基本方法。

AbstractCollection实现了Collection中的一些方法,同时也保留了一些抽象方法供具体类来实现,因此AbstractCollection类是一个抽象类。

AbstractCollection的抽象方法:

public abstract Iterator<E> iterator();
public abstract int size();

AbstractCollection类实现的方法:

1、isEmpty方法

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

由于size()是抽象方法,因此不同的类会有不同的实现。

2、add方法

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

AbstractCollection不允许添加单个元素,如果添加单个元素会报不支持操作异常。

3、allAll方法

public boolean addAll(Collection<? extends E> paramCollection) {
    int i = 0;
    //遍历要添加的集合,对于每个要添加的元素,调用add方法添加到当前集合中
    Iterator localIterator = paramCollection.iterator();
    while (localIterator.hasNext()) {
        if (add(localIterator.next()));
        i = 1;
    }
    return i;
}

4、contains方法

public boolean contains(Object paramObject) {
    //调用本类iterator方法生成迭代器
    Iterator localIterator = iterator();
    //如果入参为null,则使用迭代器判断当前集合是否含有null元素
    if (paramObject == null)
        while (true) {
            if (!(localIterator.hasNext()))
                break label53;
            if (localIterator.next() == null)
                return true;
        }
    //集合非空,使用equals比较迭代器和入参是否相同,这里paramObject需要重写equals方法。
    while (localIterator.hasNext()) {
        if (paramObject.equals(localIterator.next()))
            return true;
    }
    label53 : return false;
}

5、containsAll方法

public boolean containsAll(Collection<?> paramCollection) {
    //调用入参paramCollection集合的iterator得到入参集合的迭代器
    Iterator localIterator = paramCollection.iterator();
    //遍历入参集合所有元素,调用本集合contains方法(遍历本集合元素,使用equals比较),因此时间复杂度为n^2
    while (localIterator.hasNext())
        if (!(contains(localIterator.next())))
            return false;
    return true;
}

6、remove方法

public boolean remove(Object paramObject) {
    //调用本地迭代器方法
    Iterator localIterator = iterator();
    //如果要删除的参数为空,则判断当前集合中是否包含null
    if (paramObject == null) {
        do
            if (!(localIterator.hasNext()))//如果本集合没有元素,直接返回false
                break label65;
        while (localIterator.next() != null);
        localIterator.remove();//删除第一个为null的元素
        return true;
    }
    //入参即要删除的元素不为空
    do
        if (!(localIterator.hasNext()))//如果当前集合为空,返回false
            break label65;
    //如果当前集合元素与删除元素不等,则一直遍历,直到找到相等元素并删除,并返回true
    while (!(paramObject.equals(localIterator.next())));
    localIterator.remove();
    return true;

    label65 : return false;
}

7、removeAll方法

public boolean removeAll(Collection<?> paramCollection) {
    int i = 0;
    //与addAll方法逻辑相同,遍历要删除的集合,然后对于每个元素执行删除动作
    Iterator localIterator = iterator();
    while (localIterator.hasNext()) {
        //删除之前要执行一下contains方法,判断是否包含,如果不包含就不需要删除了
        if (paramCollection.contains(localIterator.next()));
        localIterator.remove();
        i = 1;
    }

    return i;
}

8、finishToArray方法
该方法将一个迭代器中的元素合并入一个已知数组并返回合并之后的数组,因此该方法接受两个参数,一个是源数组 T[] paramArrayOfT;另一个是待合并的迭代器Iterator<?> paramIterator;方法返回值T[],即合并之后的数组。

private static <T> T[] finishToArray(T[] paramArrayOfT,Iterator<?> paramIterator) {
    //保存源paramArrayOfT数组中实际存储元素的个数,每次进入循环,数组实际大小将执行+1操作
    int i = paramArrayOfT.length;
    //遍历需要合并入数组的集合paramIterator
    while (paramIterator.hasNext()) {
        //使用j记录当前数组paramArrayOfT的容量
        int j = paramArrayOfT.length;
        /**
        * i和j相等,说明数组已经满了,需要数组进行扩容操作
        * 扩容的规则是:数组的当前容量 (j/2+1)*3,假设数组当前容量为10,经过运算,数组的容量将扩容为18,容量不到源容量的2倍
        * 扩容之后,判断
        */
        if (i == j) {
            int k = (j / 2 + 1) * 3;
            //这里判断k<=j的用意在于如果j过大,接近int的上边界,那么j在扩大超越int界限会变为负数,即扩容后的k比j要小
            //如果发生这种情况,判断j如果等于int上界,报错,因为数组不能再进行扩容了,否则将新数组的大小指定为int上界-1=2147483647
            if (k <= j) {
                if (j == 2147483647) {
                    throw new OutOfMemoryError(
                            "Required array size too large");
                }
                k = 2147483647;
            }
            //经过以上步骤,已经完成数组的扩容操作,接下来将paramArrayOfT指向扩容并拷贝元素后的新数组
            paramArrayOfT = Arrays.copyOf(paramArrayOfT, k);
        }
        //i和j不等,说明数组还没满,此时直接将迭代器下一个元素插入数组下一个位置即可
        paramArrayOfT[(i++)] = paramIterator.next();
    }
    /*判断数组i和paramArrayOfT.length是否相等,即判断经过扩容后的数组是否满容量:
        如果相等,说明当前数组paramArrayOfT没有空闲位置,直接返回即可
        如果不相等,说明数组paramArrayOfT经过扩容后,有空余位置未填入元素,此时需要返回实际大小的一个新数组。
    */
    return ((i == paramArrayOfT.length) ? paramArrayOfT : Arrays.copyOf(
            paramArrayOfT, i));
}

思考:该方法是一个私有方法,目的是处理toArray方法的再多线程情况下的操作,具体请看9和10。

9、toArray方法

public Object[] toArray() {
    //新建一个size容量的Object数组
    Object[] arrayOfObject = new Object[size()];
    //调用本地迭代器并遍历,将结合中每一个元素放入数组
    Iterator localIterator = iterator();
    for (int i = 0; i < arrayOfObject.length; ++i) {
        //如果迭代器到达结尾,返回实际元素容量的数组拷贝
        if (!(localIterator.hasNext()))
            return Arrays.copyOf(arrayOfObject, i);
        arrayOfObject[i] = localIterator.next();
    }
    //如果执行上述代码迭代器还有元素,调用finishToArray将迭代器中元素合并入数组并返回
    //否则直接返回arrayOfObject数组
    return ((localIterator.hasNext()) ? finishToArray(arrayOfObject,
            localIterator) : arrayOfObject);
}

为什么执行到最后还需要判断迭代器是都还存在元素呢,上面不是基于size()函数创建了数组了么,我们知道一旦迭代器创建,再添加元素是不允许的,会报java.util.ConcurrentModificationException。但是假设线程A执行完第一行,此时线程A已经创建了size元素大小的数组,在调用iterator方法之前,线程B向当前集合新添加了元素,线程A继续执行创建迭代器,此时数组是添加元素之前的集合大小,迭代器确是添加元素之后的迭代器,那么集合的实际大小已经扩大了,线程A创建的数组长度显然比实际元素少,此时就需要调用方法8-finishToArray再次执行拷贝,AbstractCollection不是线程安全的,但是多线程操作时,toArray不会产生问题。

10、toArray重载方法:该方法接受一个已知数组paramArrayOfT,将集合中所有元素填入这个数组并返回,如果paramArrayOfT中有元素,集合中的元素会从前向后覆盖,如果数组中还存在之前数组,那返回的数组就是两部分数组,从0到size-1存放的是集合中的数据,size到paramArrayOfT.length-1存储的是paramArrayOfT故有数据,容易造成数据混乱,因此不建议入参paramArrayOfT中存入数据,一般传入空数组。

public <T> T[] toArray(T[] paramArrayOfT) {
    //集合中的元素个数
    int i = size();
    /**
    * 如果入参paramArrayOfT大小比集合元素个数小,新建一个数组实例,大小为集合大小
    * 元素类型与paramArrayOfT相同。
    */
    Object[] arrayOfObject = (paramArrayOfT.length >= i)
            ? paramArrayOfT
            : (Object[]) (Object[]) Array.newInstance(paramArrayOfT
                    .getClass().getComponentType(), i);
    //调用迭代器方法准备遍历添加
    Iterator localIterator = iterator();

    for (int j = 0; j < arrayOfObject.length; ++j) {
        //如果迭代器没有多余元素,返回实际元素数量的数组拷贝
        if (!(localIterator.hasNext())) {
            if (paramArrayOfT != arrayOfObject)
                return Arrays.copyOf(arrayOfObject, j);
            arrayOfObject[j] = null;
            return arrayOfObject;
        }
        //否则直接将迭代器下一个元素装进数组
        arrayOfObject[j] = localIterator.next();
    }
    //这里还是多线程的问题,会造成size装满了,但是迭代器中还有元素,需要调用finishToArray进行处理
    return ((localIterator.hasNext()) ? finishToArray(arrayOfObject,
            localIterator) : arrayOfObject);
}

11、retainAll方法:该方法用于保留入参paramCollection中不存在的元素。

public boolean retainAll(Collection<?> paramCollection) {
    int i = 0;
    //调用集合迭代器,并遍历
    Iterator localIterator = iterator();
    while (localIterator.hasNext()) {
        //如果paramCollection中包含当前元素,就删除,最后剩下的就是要保留的所有元素
        if (!(paramCollection.contains(localIterator.next())));
        localIterator.remove();
        i = 1;
    }

    return i;
}

12、clear方法:用于清空当前集合

public void clear() {
    //调用迭代器并遍历,挨个删除
    Iterator localIterator = iterator();
    while (localIterator.hasNext()) {
        localIterator.next();
        localIterator.remove();
    }
}

13、toString方法

public String toString() {
    //调用iterator方法,准备遍历。
    Iterator localIterator = iterator();
    //如果迭代器没有元素,说明集合为空,返回"[]"
    if (!(localIterator.hasNext())) {
        return "[]";
    }
    StringBuilder localStringBuilder = new StringBuilder();
    localStringBuilder.append('[');
    //将集合中所有元素拼接,返回[1,2,3]诸如这种形式的字符串
    while (true) {
        Object localObject = localIterator.next();
        localStringBuilder.append((localObject == this)
                ? "(this Collection)"
                : localObject);
        if (!(localIterator.hasNext()))
            return ']';
        localStringBuilder.append(", ");
    }
}

此处注意需要集合中元素类实现toString方法,否则返回的字符串中将是各元素地址拼成的字符串,我们可以执行如下方法:

List<Integer> list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    System.out.println(list);
运行结果:
[1, 2, 3]

我们可以直接打印list、set,系统可以正确打印元素都是因为AbstractCollection类的toString方法的功劳。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容