java 为什么遍历的时候不能删除元素详解编程语言
阿里巴巴java开发手册的建议
在看阿里巴巴java开发手册时,有一条建议是这样的。
【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
反例:
List String a = new ArrayList String a.add("1"); a.add("2"); for (String temp : a) { if("1".equals(temp)){ a.remove(temp);
说明:这个例子的执行结果会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
正例:
Iterator String it= a.iterator(); while(it.hasNext()){ String temp = it.next(); if(删除元素的条件){ it.remove();
以上就是手册的建议内容。
我的测试试验结果表明,为“1”时没有任何错误,“2”时,出现ConcurrentModificationException,也就是并发修改异常。
异常信息如下
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at com.jun.javase.TestIterator.main(TestIterator.java:16)
为了看清foreach的真面目,这里将class文件反编译,就明白运行过程了
List a = new ArrayList(); a.add("1"); a.add("2"); Iterator i$ = a.iterator(); if(!i$.hasNext()) break; String temp = (String)i$.next(); if("1".equals(temp)) a.remove(temp); } while(true);
foreach遍历集合,其实是走的Iterator,首先判断hasNext(),如果没有了则终止循环,否则next()获取元素时,next()时,都要check一下集合元素个数是否变化了,如果变化了,则抛出异常。
查看源代码,Itr是ArrayList的内部类,实现了Iterator接口
private class Itr implements Iterator E { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size;//游标不等于元素个数就是还有下一个 public E next() { checkForComodification();//check是否并发修改 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();
modCount是集合添加元素、删除元素的次数,expectedModCount是预期的修改次数。因为我们增加了2个元素,所以,modCount是2,预期修改的次数expectedModCount也是2
现在解释一下,为什么“1”时没有出错,2是却出错了。
“1”时,第一次循环后,光标变为1,第一个元素已经移除,所以元素个数是1;进入第二次循环,判断hasNext()由于光标=元素个数,所以,终止循环,所以,就结束了。
“2”时,第二次循环后,光标变为2,此时元素个数是1,光标!=元素个数,所以进入next()方法,next()中调用checkForComodification,modCount是3,expectedModCount是2,两者不相等,所以抛出异常。
遍历集合删除元素的正确方式 迭代器方式移除那么如果我们既想遍历元素又想增加/删除元素怎么办?
第一个方法,可以使用迭代器的remove方法,而不是集合的remove方法。这是因为,迭代器的remove方法会修改expectedModCount,从而使modCount与之相等
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();
迭代器操作元素样例,这种不会出现并发修改异常。
Iterator it = list.iterator(); while(it.hasNext()){ it.next(); it.remove();
注意,next()方法,不只返回元素,也改变了光标位置。所以如果执行两次next()就会跳过两个元素。
for(int i=0; i list.size; i++)正序方式有同学会问,如果使用
for(int i=0; i list.size(); i++){
会如何呢?使用这种方法,如果删除元素,那么会跳过某些元素。可以重置index的方式来修正。比如
for(int i=0; i list.size(); i++){ if(flag){ //删除元素 //修正index,否则不会出现异常,但是会略过某些元素 i=i-1;
关于这一点,这里解释一下。比如,咱们要遍历一个集合,如果等于1就移除掉,否则全部加1,代码如下
List String a = new ArrayList String a.add("1"); a.add("2"); a.add("3"); for (int i = 0; i a.size(); i++) { String e = a.get(i); if("1".equals(e)){ a.remove(i); // i=i-1; 修正index }else{ a.set(i,e+"1"); System.out.println(a);
运行结果
[2, 31]
结果出人意料吧?为什么2没有加1?!简单来说,移除元素后,后边的元素会往前移动,将空白填充上。
第一次循环,i=0,移除了第一个元素后,i变为1,但是,同时,由于1的空出,2、3都往前移动一个位置,所以,索引位置不再是1、2,而是0、1
第二次循环,i=1,3的索引是1,所以只将3加1,而2已经过去了。
由正序方式的缺点为出发点思考,如果倒序的遍历,中间即使有删除也不会漏掉元素。这里就不再举例子了。
List String a = new ArrayList String a.add("1"); a.add("2"); a.add("3"); for (int i = a.size()-1; i i--) { String e = a.get(i); if("3".equals(e)){ a.remove(i); }else{ a.set(i,e+"1"); System.out.println(a);参考
java核心技术(卷1)
9年全栈开发经验,请关注个人公众号原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/20257.html
cjavaxml相关文章
- java用什么编译器_Java用Java编译「建议收藏」
- java分层打印二叉树_基于Java的二叉树层序遍历打印实现
- java calendar获取年_Java Calendar获取年、月、日、时间,设置年、月、日
- MySQL字段类型如何转为java_Java JDBC中,MySQL字段类型到JAVA类型的转换
- Java遍历json_java处理json数据
- java list 转json 字符串_JSON的String字符串与Java的List列表对象的相互转换
- Java数组循环_java遍历object数组
- 【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )
- java并发编程(1):Java多线程-基本线程类-基础知识复习笔记
- java压缩与解压(Java.util.zip)详解编程语言
- Java创建二叉树并遍历的代码详解编程语言
- Java通过递归进行二叉树遍历详解编程语言
- Java学习笔记之六java三种循环(for,while,do……while)的使用方法及区别详解编程语言
- Java学习笔记之五java数组详解编程语言
- Java学习笔记之二java标识符命名规范详解编程语言
- Java 遍历类中的属性详解编程语言
- Java中遍历Map对象详解编程语言
- java 标准输出与标准错误 out与 err 区别 用法 联系 java中的out与err区别 System.out和System.err的区别 System.out.println和System.err.println的区别 Java重定向System.out和System.err详解编程语言
- JAVA遍历Map的方法详解编程语言
- 解决Java程序连接MySQL数据库的方法(java链接mysql数据库)
- 注册MySQL,让你的Java技能更上一层楼(java注册mysql)
- Java异步MySQL:开启数据处理新时代(java异步mysql)
- 【Java】遍历目录下的所有文件详解编程语言
- 使用Java操作Redis数据库(java中使用redis)
- 工具Linux上使用Java开发的利器:选择指南(linux选择java)
- 使用Java连接MySQL实现查询功能(java连接mysql查询)
- 数据库Java编程修改Oracle数据库的实践(java修改oracle)
- MySQL和Java结合的必备工具MySQL下载Java(mysql下载java)