List的remove的坑点

288 阅读1分钟

1、你真的会用List的remove方法吗?

首先看下面这个例子:

public class ListRemoveDemo {
​
    public static void main(String[] args) {
        List<String> sourceList = new ArrayList<>();
        sourceList.add("Rose");
        sourceList.add("Jack");
        sourceList.add("Cooper");
        sourceList.add("Jay");
​
        //此时打印sourceList输出: [Rose,Jack,Cooper,Jay]
​
        for (int i = 0; i < sourceList.size(); i++) {
            String curStr = sourceList.get(i);
            if (isNeedRemove(curStr)) {
                sourceList.remove(curStr);
            }
        }
​
        //此时打印sourceList输出: [Rose,Cooper,Jay]
​
    }
​
    private static boolean isNeedRemove(String s) {
        if ("Jack".equalsIgnoreCase(s)) {
            return true;
        }
​
        return false;
    }
}

上面例子中,使用isNeedRemove方法来决定List哪些元素应该被移除,从上面的输出结果来看, 原始list:[Rose,Jack,Cooper,Jay] 删除Jack后 最后的list: [Rose,Cooper,Jay] 乍一看,非常完美了达到了想要实现的效果。 ​

再看下面这个例子:

public class ListRemoveDemo {
​
    public static void main(String[] args) {
        List<String> sourceList = new ArrayList<>();
        sourceList.add("Rose");
        sourceList.add("Jack");
        sourceList.add("Jack");
        sourceList.add("Jay");
​
        //此时打印sourceList输出: [Rose,Jack,Jack,Jay]
​
        for (int i = 0; i < sourceList.size(); i++) {
            String curStr = sourceList.get(i);
            if (isNeedRemove(curStr)) {
                sourceList.remove(curStr);
            }
        }
​
        //此时打印sourceList输出: [Rose,Jack,Jay]
​
    }
​
    private static boolean isNeedRemove(String s) {
        if ("Jack".equalsIgnoreCase(s)) {
            return true;
        }
​
        return false;
    }
}

在这个例子中,sourceList为[Rose,Jack,Jack,Jay],想要达到的效果是删除为Jack的元素输出为[Rose,Jack,Jay],而实际输出结果为[Rose,Jack,Jay]。 很显然,有一个Jack没有成功被remove掉。 ​

这里的例子只是简单的模拟出remove的坑,想象一下,isNeedRemove方法是某个业务判断,对于List中的不同的元素可能都会返回需要从列表中删除,这时就会遇到上面例子中存在的问题。 ​

那么,问题出在哪?

其实,原因很简单,通过查看ArrayList的remove的源码可以知道,remove过程实际上是会进行一次数组“平移”,找到需要删除的元素的index,将[index+1, list.size()-1]向前移动一位。 ​

  // ArrayList.java
  // remove方法最终会调用fastRemove
​
  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
    }

通过System.arraycopy方法将index + 1位置及其后的元素向前移动一位,最后将数据elementData末尾的元素设置为null,且size减1。

通过这里可以看出,每次remove一个元素后,后面的节点元素会整体前移一位,而使用for循环的i却加1了,上面的案例的remove过程如下:

未命名文件.jpg

2.怎么样正确的使用List的remove方法?

1)倒序删除法

上面错误的方式是从前往后进行判断后remove,从List的尾部遍历到头部进行判断remove ​

2)新建一个List

新建一个List来存放需要保留的元素 ​

3)使用迭代器Iterator

public class ListRemoveDemo {
​
    public static void main(String[] args) {
        List<String> sourceList = new ArrayList<>();
        sourceList.add("Rose");
        sourceList.add("Jack");
        sourceList.add("Cooper");
        sourceList.add("Jay");
​
        //此时打印sourceList输出: [Rose,Jack,Jack,Jay]
​
        Iterator<String> iterator = sourceList.iterator();
​
        while (iterator.hasNext()) {
            String next = iterator.next();
​
            if (isNeedRemove(next)) {
                // 注意这里是iterator的remove
                iterator.remove();
            }
        }
​
        //此时打印sourceList输出: [Rose,Jay]
​
    }
​
    private static boolean isNeedRemove(String s) {
        if ("Jack".equalsIgnoreCase(s)) {
            return true;
        }
​
        return false;
    }
}