前言:
现时你们对“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元素