龙空技术网

关于List的remove()方法陷阱,你可能没遇到过

洪生鹏 305

前言:

现时你们对“foreach循环删除list元素”大致比较着重,兄弟们都想要了解一些“foreach循环删除list元素”的相关资讯。那么小编同时在网摘上收集了一些对于“foreach循环删除list元素””的相关内容,希望咱们能喜欢,我们快快来了解一下吧!

小爱今天去面试了,有一道笔试题没有答对,估计黄了。

题目是这样的

1List<Integer> list = new ArrayList<>();2list.add(1);3list.add(2);4for (Integer temp : list) {5    if(1==temp){6        list.remove(temp);7    }8}9System.out.println(list);

输出结果:[2]

要是把上面第5行的1改成2

输出结果是什么:

小爱选择:[1]

面试官说你回去试试。

回来试了下,抛出异常

 Exception in thread "main" java.util.ConcurrentModificationException

这是什么情况?

原来foreach 的写法实际上是对的 Iterable、hasNext、next方法的简写。于是查找List.iterator()源码,跟踪iterator()方法,发现该方法返回了 Itr 迭代器对象。

 public Iterator<E> iterator() {    return listIterator();}

Itr 类源码

 1private class Itr implements Iterator<E> { 2    /** 3     * Index of element to be returned by subsequent call to next. 4     */ 5int cursor = 0; 6 7 8/** 9 * Index of element returned by most recent call to next or10 * previous.  Reset to -1 if this element is deleted by a call11 * to remove.12 */13int lastRet = -1;141516/**17 * The modCount value that the iterator believes that the backing18 * List should have.  If this expectation is violated, the iterator19 * has detected concurrent modification.20 */21int expectedModCount = modCount;222324public boolean hasNext() {25    return cursor != size();26}272829public E next() {30    checkForComodification();31    try {32        int i = cursor;33        E next = get(i);34        lastRet = i;35        cursor = i + 1;36        return next;37    } catch (IndexOutOfBoundsException e) {38        checkForComodification();39        throw new NoSuchElementException();40    }41}424344public void remove() {45    if (lastRet < 0)46        throw new IllegalStateException();47    checkForComodification();484950    try {51        AbstractList.this.remove(lastRet);52        if (lastRet < cursor)53            cursor--;54        lastRet = -1;55        expectedModCount = modCount;56    } catch (IndexOutOfBoundsException e) {57        throw new ConcurrentModificationException();58    }59}606162final void checkForComodification() {63    if (modCount != expectedModCount)64        throw new ConcurrentModificationException();65 }66}

通过源码我们知道Itr 是 ArrayList 中定义的一个私有内部类,在 next、remove方法中都会调用checkForComodification 方法,该方法的作用是判断 modCount != expectedModCount是否相等,要是不相等就抛出ConcurrentModificationException异常。

每次正常执行remove 方法后,都会对执行expectedModCount = modCount赋值,保证两个值相等,那么问题基本上已经清晰了,在foreach 循环中 执行 list.remove(item);对list对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,所以抛出了ConcurrentModificationException异常。

原来坑这么大,怪不得面试官会提这样的问题。

关于List的remove()方法陷阱,你可能也遇到过。

Java的List在删除元素时,一般会用list.remove(object)/remove(i)方法。在使用时,容易触碰陷阱,得到意想不到的结果。

今天整理出来与大家分享。下面我们写个demo来实践下。

先初始化list

 1package com.peng.tree; 2import java.util.ArrayList; 3import java.util.List; 4 5 6public class ListRemoveTest { 7  public static void main(String[] args) { 8    List<Integer> list = new ArrayList<>(); 9    list.add(1);10    list.add(2);11    list.add(3);12    list.add(4);13    list.add(4);14    list.add(5);15    System.out.println(list);16  }17}

毋庸置疑输出结果:[1, 2, 3, 4, 4, 5]

list中含有两个元素4,我们要把元素4删除。

如果我们用for循环遍历List删除指定元素 ,例如这样:

1    for(int i=0;i<list.size();i++){2        if(list.get(i)==4) {3            list.remove(i);4        }5    }6    System.out.println(list);

输出结果: [1, 2, 3, 4, 5]

细心的你会发现,元素4只删除了一个,还有一个元素4并没有删除, 可见这种做法是不可取的。

为什么元素4只删除了一个?因为List调用remove(index)方法后,会移除index位置上的元素,index之后的元素就全部依次左移。

既然这样,那我们是否可以在删除元素后同步调整索引或者倒序遍历删除元素?

先尝试删除元素后同步调整索引

1for(int i=0;i<list.size();i++){2 if(list.get(i)==4) {3   list.remove(i);4   i--;5   }6 }7System.out.println(list);

输出结果:[1, 2, 3, 5]

两个元素4都正常删掉了。

再来看看倒序遍历List删除元素

   for(int i=list.size()-1;i>=0;i--){        if(list.get(i)==4){            list.remove(i);        }    }    System.out.println(list);

输出结果:[1, 2, 3, 5] 也是可行的。

前面我们提到用foreach,出现了异常,我们再来验证下。

foreach遍历List删除元素

    for(Integer i:list){        if(i==4) {            list.remove(i);        }    }    System.out.println(list);

结果不出所料,抛出异常:

  Exception in thread "main" java.util.ConcurrentModificationException

用迭代删除List元素

   Iterator<Integer> it=list.iterator();    while(it.hasNext()){        if(it.next()==4){            it.remove();        }    }    System.out.println(list);

输出结果:[1, 2, 3, 5]

Iterator.remove() 方法会在删除当前迭代对象的同时,会保留原来元素的索引。所以用迭代删除元素是最保险的方法,建议大家使用List过程中需要删除元素时,使用这种方式。

我们再用迭代遍历,用list.remove(i)方法删除元素试试。

java.util.ConcurrentModificationException

结果抛出异常:

java.util.ConcurrentModificationException

原理同上。

还有一点我们需要注意的是,List删除元素时,需要留意Integer类型和int类型的区别。

例如上述Integer的list,如果我们想要直接删除元素4,要是我们这样写

 list.remove(2); System.out.println(list);

输出结果:[1, 2, 4, 4, 5]

从输出结果得知,并没有把元素4删掉,而是3元素删掉了,List删除元素时传入数字时,默认按索引删除。如果需要删除Integer对象,调用remove(object)方法,需要传入Integer类型。

   list.remove(new Integer(2));   System.out.println(list)

输出结果:[1, 3, 4, 4, 5]

这才是真正删除掉元素2。

总结:

用for循环遍历List删除元素时,需要注意索引会左移的问题。List删除元素时,为避免异常,建议使用迭代器iterator的remove方式。List删除元素时,默认按索引删除,而不是对象删除。不要在 foreach 循环里进行元素的 remove 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

今天的分享就到这里,要是本文对你学习有帮助。

文|洪生鹏 擅长java,qt、Android、小程序平台开发,技术交流请@洪生鹏

标签: #foreach循环删除list元素