list移除特定条件参数

曾经和之前公司leader聊过,如何面试1年左右工作经验的java开发。当时我的leader当场说了一个他常用的面试笔试问题,给定list内含有1,2,3,4,5五个值,如何移除值为2的参数。

从java1.6的角度来看,这是一个很容易出坑的问题,毕竟如果你代码写成以下这样,就说明你上套了:

        ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
        int size = list.size();
        for (int i = 0; i < size; i++) {
            if (list.get(i) == 2) {
                list.remove(i);
            }
        }

这是刚开始写java的时候最容易遇到的坑,因为list执行过remove之后,list本身的size已经变成了4,而当循环内i走到4时,此时list长度为4,是获取不到下标为4的参数的,具体源码如下:

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }


    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

当然,开发一段时间后有些人可能习惯用foreach,写法如下:

       ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
       for (Integer i : list) {
            if(i==2){
                list.remove(i);
            }
        }

代码量减少了,而且也不报错,万事大吉?no,no,no,这种写法最坑的地方就是正常不报错,一旦移除的值在最后一位,例如i==5,立刻报错,为了便于理解,我们无妨吧foreach循环转换成java编译时自动转换后的样子,如下:

        ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);

        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            if(i==5){
                list.remove(i);
            }
        }

简单来讲,当list.remove执行的时候了,list内的数组长度变成了4,但是iterator的依旧会尝试取第五位。所以产生了问题(jdk在进入此前,很巧妙的记录了list与iterator的变化次数,并进行对比防止此问题)。但是为什么不移除最后一位的时候就不会有问题,这是因为iterator.hasNext方法会比对当前位是否不等于list长度,当移除中间变量时,走到最后倒数第二位时,下一次循环当前位数为4,数组长度为4,跳出了循环,即5这个参数其实不会参加循环,有兴趣的同学可以再循环内打印i即可观察到(debug当然也可以)。

但其实jdk设计的时候已经考虑过此问题,所以实际上iterator本身提供了remove方法,他会同步修改各参数,iterator相关源码如下

        public boolean hasNext() {
        // cursor为迭代器当前位置,size为list长度
return cursor != size; } public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } final void checkForComodification() {
       // modCount为list改变次数,exceptedModCount为迭代器预期改变次数
if (modCount != expectedModCount) throw new ConcurrentModificationException(); } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }

最后,附上1.6年代的有效代码如下:

        ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);

        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            if(i==5){
                iterator.remove();
            }
        }

 

当然,到此还远远不是结束的时候,我们知道java1.8中引入了函数式编程,完美跳过此坑,我们可以使用以下代码

        List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);

        list= list.stream().filter(e -> e != 5).collect(Collectors.toList());

上述方法的好处是使用stream表达式可以继续进行其他处理,如果单纯只是移除指定值,我们还可以使用jdk1.8的自带方法,如下:

        List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);

        list.removeIf(e->e==5);

 

结束前附上jdk的Collections与ArrayList对removeIf的实现,可以看到jdk中ArrayList其实并没有使用迭代器进行remove。

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }



public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

 

posted @ 2019-10-12 01:00  豆豆323  阅读(1214)  评论(0编辑  收藏  举报