概述

对于List接口及其实现是日常开发中最常用的容器之一,对于List集合的并集、差集等操作也同样是频率极高。但是往往越是简单、常用的内容越容易让人放松警惕。

一步步把“栗子”丢进坑里

我们先说个简单的,我们已知有两个集合A和B,A={0,1,2,3,4,5}, B{2,3,4},现要求得到 A-B ,即 预期为 A = {0,1,5}

太简单了是不是,

List<Integer> list1 = new ArrayList<>();
        list1.add(0);
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        list1.add(5);

        List<Integer> list2 = new ArrayList<>();
        list2.add(2);
        list2.add(3);
        list2.add(4);

        list1.removeAll(list2);
        System.out.println(list1);

如果我们对上面的需求稍加改动,我现在告诉大家,集合A={1,1,1,1,1,1},集合B={1,1,1},再求A-B,预期结果为 A = A-B = {1,1,1},你想如何实现呢?

相信有的朋友此时就掉进了坑里,不是和上面一样removeAll()一下就好了吗?!强大如ChatGPT 也犯了同样的错误。

list remove失效 list的removeall_List

ps:有的同学可能会问为啥你的ChatGPT可以在微信上? 文章末尾会有体验的入口哦!不要急,我们先把今天的主题聊透。如果针对上面的要求依然采用removeAll( )方法,你得到的只会是一个空集。

list remove失效 list的removeall_List_02


正确的写法应该是将B集合(list2)遍历,在循环中每次让A集合(list1)调用remove()方法:

List<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(1);
        list1.add(1);
        list1.add(1);
        list1.add(1);

        List<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(1);

        list2.forEach(e->list1.remove(e));
        System.out.println(list1);

list remove失效 list的removeall_html_03

不止于信

下面我来从源码角度详解一下remove()和removeAll()的本质区别,事实上removeAll()绝不仅仅是我们想当然的以为的:遍历执行remove()这么简单。 编程中最忌讳的就是把“我记得”、“我以为”挂在嘴边而写着原理的源码、接口注释、算法原理却看都没看过一眼。
我们现来看一下List顶级接口对这两个方法的解释:

remove(Object obj)

注意,remove方法还有一个实现是传int index,不是今天的重点所以不做展开。

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present (optional operation).  If this list does not contain
     * the element, it is unchanged.  More formally, removes the element with
     * the lowest index <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list changed
     * as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     * @throws ClassCastException if the type of the specified element
     *         is incompatible with this list
     * (<a href="Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified element is null and this
     *         list does not permit null elements
     * (<a href="Collection.html#optional-restrictions">optional</a>)
     * @throws UnsupportedOperationException if the <tt>remove</tt> operation
     *         is not supported by this list
     */
    boolean remove(Object o);

“Removes the first occurrence of the specified element from this list”,关键在first,移除的是集合中第一个相同的元素,我们再来看下ArrayList对于它的实现:

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);// 删除数据方法
                    return true;//删掉一个立刻返回,结束方法
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);// 删除数据方法
                    return true;//删掉一个立刻返回,结束方法
                }
        }
        return false;
    }
	/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
	public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

remove()方法是删掉了一个元素之后立刻返回的,这点很重要。

removeAll(Collection <?> c)

removeAll实际处理的是两个集合之间的关系

/**
     * Removes from this list all of its elements that are contained in the
     * specified collection (optional operation).
     *
     * @param c collection containing elements to be removed from this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
     *         is not supported by this list
     * @throws ClassCastException if the class of an element of this list
     *         is incompatible with the specified collection
     * (<a href="Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if this list contains a null element and the
     *         specified collection does not permit null elements
     *         (<a href="Collection.html#optional-restrictions">optional</a>),
     *         or if the specified collection is null
     * @see #remove(Object)
     * @see #contains(Object)
     */
    boolean removeAll(Collection<?> c);

“Removes from this list all of its elements that are contained in the specified collection”,它旨在删除掉A集合中所有在B集合中出现过的元素。我们来看一下ArrayList对它的实现:

/**
     * Removes from this list all of its elements that are contained in the
     * specified collection.
     *
     * @param c collection containing elements to be removed from this list
     * @return {@code true} if this list changed as a result of the call
     * @throws ClassCastException if the class of an element of this list
     *         is incompatible with the specified collection
     * (<a href="Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if this list contains a null element and the
     *         specified collection does not permit null elements
     * (<a href="Collection.html#optional-restrictions">optional</a>),
     *         or if the specified collection is null
     * @see Collection#contains(Object)
     */
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
	            //complement = false,当elementData中的元素不在c中时,用这个元素替换前面与其相同的元素
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                // 移除成功后,w为此时的size
                size = w;
                modified = true;
            }
        }
        return modified;
    }

这里给大家画图解释下removeAll的原理

list remove失效 list的removeall_List_04


它的核心思想是将重复的元素挪到集合后方,然后删除这部分重复的得到差集。这也就解释了为什么六个1和三个1 如果用 removeAll方法取差集会得到一个空集了。