foreach迭代ArrayList时,真的不能删除元素吗?
ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况。这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.ConcurrentModificationException异常。比如下面的代码就会抛这个异常:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");for (String item: list) {if (item.equals("3")) {list.remove(item);}
}
System.out.println(Arrays.toString(list.toArray()));
那是不是在foreach循环时删除元素一定会抛这个异常呢?答案是否定的。
见这个代码:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");for (String item: list) {if (string.equals("4")) {list.remove(item);}
}
System.out.println(Arrays.toString(list.toArray()));
这段代码和上面的代码只是把要删除的元素的索引换成了4,这个代码就不会抛异常。为什么呢?
接下来先就这个代码做几个实验,把要删除的元素的索引号依次从1到5都试一遍,发现,除了删除4之外,删除其他元素都会抛异常。接着把list的元素个数增加到7试试,这时候可以发现规律是,只有删除倒数第二个元素的时候不会抛出异常,删除其他元素都会抛出异常。
好吧,规律知道了,可以从代码的角度来揭开谜底了。
首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常
其实,每次foreach迭代的时候都有两步操作():
iterator.hasNext() //判断是否有下个元素
item = iterator.next() //下个元素是什么,并赋值给上面例子中的item变量
next()方法的代码如下:
@SuppressWarnings("unchecked")
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() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
这时候你会发现这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount
modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会增加;
expectedModCount是Iterator类中特有的变量,指现在期望这个list被修改的次数是多少次,这个值在调用list.iterator()创建iterator的时候初始化为modCount,该值在iterator初始化直到使用结束期间不会改变。
iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时修改expectedModCount,这样就导致下次取值时检查到两个count不相等,从而抛出异常。
解决这个问题的一种方式是使用Iterator来操作列表:
Iterator<String> it = list.iterator();
while(it.hasNext()) {if (it.next().equals("3")) {it.remove();}
}
那么为什么这种方式不会抛出该异常呢?下面是ArrayList中内部类Itr的remove方法:
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();}
}
注意下面这句:
expectedModCount = modCount;
可以看出,在使用iterator()方法得到的Iterator对象后,通过iterator.remove方法是可以正确删除列表元素的,因为它保证了expectedModCount=modCount。
避免这个问题的另一种方法,是不使用foreach语句的for循环:
for (int i = 0; i < list.size(); ) {String s = list.get(i);if (s.equals("3")) {list.remove(i);continue;}i++;
}
回到问题上来,在使用foreach迭代ArrayList时,是可以删除任何一个元素的,且只能删除一个,而且这只能发生在迭代到倒数第二个元素的时候。比如下面的代码不会有异常:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");for (String item: list) {if (string.equals("4")) {list.remove("5"); //删除的不一定是当前元素}
}
System.out.println(Arrays.toString(list.toArray()));
其真正的原因是remove("5")这一句之后,下一次foreach语句将调用iterator.hasNext()方法,如果此时返回false,这样就不会进到next()方法里了,也就不会调用checkForComodification而导致异常了。
疑问:当循环到倒数第二个元素时,如果再多删除一个会怎样呢?比如:
for (String item: list) {if (item.equals("4")) {list.remove("1");list.remove("5");}System.out.println(Arrays.toString(list.toArray()));
}
这段代码中,list是可以被打印出来的,因为list.remove()方法可以正确执行,其结果也是正确的。但是执行完这次打印,进入下一次迭代时,又产生了checkForComodification异常,还没想明白为什么。如果哪位大牛知道,请留言。
参考:
http://rongmayisheng.com/post/%E7%A0%B4%E9%99%A4%E8%BF%B7%E4%BF%A1java-util-arraylist%E5%9C%A8foreach%E5%BE%AA%E7%8E%AF%E9%81%8D%E5%8E%86%E6%97%B6%E5%8F%AF%E4%BB%A5%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0
http://stackoverflow.com/questions/11042552/what-does-the-modcount-variable-when-debugging-the-collection
from: https://my.oschina.net/itblog/blog/422649
foreach迭代ArrayList时,真的不能删除元素吗?相关推荐
- 写一段代码在遍历 ArrayList 时移除一个元素?
今天楼主继续分享一道经典Java面试题并进行相关知识点的拓展: 上题: 写一段代码在遍历 ArrayList 时移除一个元素? 该问题的关键在于面试者使用的是 ArrayList 的 remove() ...
- 遍历ArrayList时移除重复元素失效问题
遍历ArrayList时移除重复元素失效问题 在使用ArrayList时遇到个问题,例如: public static void remove(ArrayList<String> list ...
- Python遍历列表时其中的删除元素_CodingPark编程公园
遍历列表时其中的删除元素 删除列表中的元素我们通常会用 .remove( ) 因为删除元素后,整个列表的元素会往前移动,而i却是在最初就已经确定了,是不断增大的,所以并不能得到想要的结果. 所以会造成 ...
- 通过foreach遍历ArrayList时同时修改报错分析
遍历ArrayList可以有for循环.foreach.迭代器iterator.listIterator,其中通过foreach来遍历同时修改 ArrayList时会抛出 ConcurrentModi ...
- ArrayList中remove()方法删除元素之后下标重定位的问题
需求: 有一个ArrayList数组,要求删除长度大于5的字符串,如:arr = {"ab1","123ad","bca","da ...
- foreach遍历ArrayList时的不当操作与解决
1.不当操作1:ConcurrentModificationException并发修改异常 代码: public class TestList {public static void main(Str ...
- java利用循环打印AVA_ava.util.ArrayList在foreach循环遍历时可以删除元素
ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况.这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.Concur ...
- ArrayList删除元素的细则
删除ArrayList数组中某个元素,通常会使用for循环匹配目标元素完成删除操作. public void remove(List<String> list, String str) ...
- List遍历中删除元素
List遍历主要有索引下标遍历.for循环遍历和Iterator迭代遍历,索引下标和for循环在遍历中删除元素都存在问题,Iterator迭代可以实现遍历中删除元素. 索引下标遍历 List<I ...
最新文章
- 彻底解决Webpack打包慢的问题
- 关于python2和python3除法的区别
- 监控调优工具详细参数整理
- Kafka万亿级消息实战解决方案干货
- Numpy学习笔记(三)
- apkg格式怎么打开_天正软件如何导成CAD识别格式
- 软件图标显示不正常的问题
- 分组背包+树形DP(BY LPX)
- openbsd mysql_使用OpenBSD一年的总结
- 香港美国CERA机房你怎么选择?
- Go开发 之 设计模式
- 平板插上显示无服务器,教你一招,让ipad变成免费的外置显示器!
- 美国大学计算机工程专业排名,2018美国大学计算机工程专业排名_美国大学计算机工程排名...
- response.reset() 与response.resetbuffer使用场景
- 最流行的微服务应用框架有哪些
- web开发—— 前端基础(6) ——语义化标签
- 婴幼儿蛋白质过敏怎么回事
- 车速与档位匹配关系_驾考科目三车速和档位如何匹配
- 计算机网络05局域网
- qt中mysql怎么支持事务_Qt踩坑之mysql数据库不支持事务操作?