概述
java常见小错误(二):Java迭代器iterator和for循环的区别
java常见错误系列文章
下一篇:java常见小错误(一):变量类型自动转换与强制转换
往期文章推荐:
java小技巧(三):JAVA 交集,差集,并集
java小技巧(二):进制转换
【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权);
本博客的内容来自于:java常见小错误(二):Java迭代器iterator和for循环的区别;
学习、合作与交流联系q384660495;
本博客的内容仅供学习与参考,并非营利;
文章目录
- java常见小错误(二):Java迭代器iterator和for循环的区别
- 一、常见错误
- 二、错误原因
- 三、foreach和Iterator的关系
- 四、讨论一下集合遍历的常见问题
- 1、for循环的侧漏现象
- 2、可以直接使用迭代器(会修改expectedModCount (期望集合修改次数))
- 3、可以使用java 8的新特性filter过滤不要的元素
- 4、foreach也可以,但是限制条件很多
一、常见错误
在Iterator迭代器迭代过程或者foreach循环中,调用Collection的remove(obj)方法,要么会报错(并发修改的错误),要么行为不确定。
二、错误原因
foreach底层也是使用了iterator的方法。foreach反编译结果如下:
fail-fast,即快速失败,它是Java集合的一种错误检测机制。 当多个线程对集合(非fail-safe的集合类)进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)。
同时需要注意的是,即使不是多线程环境,如果单线程违反了规则,同样也有可能会抛出改异常。
那这又和foreach循环有什么关系呢?
上文分析到了,foreach的底层是迭代器实现的,由debug发现,在迭代器中使用Iterator.next 会调用 Iterator.checkForComodification方法 ,而异常就是checkForComodification方法中抛出的。
我们直接看下checkForComodification方法的代码,看下抛出异常的原因:
-
modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
-
expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。
-
Itr是一个Iterator的实现,使用ArrayList.iterator方法可以获取到的迭代器就是Itr类的实例。
他们之间关系:
由上图可知,迭代器开始遍历之前该迭代器的expectedModCount (期望集合修改次数)已经被modCount(实际修改次数)赋值了,然而在foreach遍历中删除数据或添加数据都是对modCount(实际修改次数)的修改,又因为是调用集合类自己的方法,所以不会对expectedModCount (期望集合修改次数)进行修改(这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除或添加了),所以就造成了不等的情况,所以就会抛出一个java.util.ConcurrentModificationException(修改并发异常),用来提示用户,可能发生了并发修改。
总之,我们不能在本线程或其他线程再调用Collection的add,remove
等方法来修改集合。 要保证遍历数据的稳定性。
三、foreach和Iterator的关系
for each以用来处理集合中的每个元素而不用考虑集合定下标。就是为了让用Iterator简单。但是删除的时候,区别就是在remove,循环中调用集合remove会导致原集合变化导致错误,而应该用迭代器的remove方法。
使用for循环还是迭代器Iterator对比
- 采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快
- 采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快
- 从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合.
- 而使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.(还是说明了一点遍历和集合本身分离了)
四、讨论一下集合遍历的常见问题
1、for循环的侧漏现象
代码如下:
结果:
由上图可知我要删除集合中的chengxi,但是删除后还有,这就是漏删了,那这又是为什呢?
因为普通for循环是根据索引删除的,由于两个相同值在相邻的位置,当删除第一个值之后,集合发生改变要重新排序索引(因为集合发生改变他的的底层**Object[]**要做位移操作,这里是要向前位移一个索引),所以后面那个要被删除的值就被挪到了删除值的索引位置,从而避免了删除也就造成了漏删。
当然也可以解决,在每次删除值的时候让索引自减就好了,最好是倒序遍历,还有equals方法也要倒着写防止空指针。
2、可以直接使用迭代器(会修改expectedModCount (期望集合修改次数))
// 迭代器循环遍历
List<String> stringListFor = getStringList();
System.out.println("删之前:"+stringListFor);
Iterator<String> iterator = stringListFor.iterator();
while (iterator.hasNext()){
if(iterator.next().equals("chengxi")){
iterator.remove();
}
}
3、可以使用java 8的新特性filter过滤不要的元素
// java 8新特性filter循环遍历
List<String> stringListFor = getStringList();
System.out.println("删之前:"+stringListFor);
List<String> stringList8 = stringListFor.stream().filter(s -> !s.equals("chengxi")).collect(Collectors.toList());
System.out.println("删之后:"+stringList8);
4、foreach也可以,但是限制条件很多
首先你很明确只删除一个元素,而且删除之后就直接结束循环,避免下一次使用Iterator.next就不会抛出异常了。
//增强for循环遍历
List<String> stringListForeach = getStringList();
System.out.println("遍历之前"+stringListForeach);
for (String s:stringListForeach) {
if(s.equals("程熙")){
stringListForeach.remove(s);
}
//删除后就结束遍历,避免抛异常
break;
}
System.out.println("遍历之后"+stringListForeach);
最后
以上就是顺心大雁为你收集整理的java常见小错误(二):Java迭代器iterator和for循环的区别java常见小错误(二):Java迭代器iterator和for循环的区别一、常见错误二、错误原因三、foreach和Iterator的关系四、讨论一下集合遍历的常见问题的全部内容,希望文章能够帮你解决java常见小错误(二):Java迭代器iterator和for循环的区别java常见小错误(二):Java迭代器iterator和for循环的区别一、常见错误二、错误原因三、foreach和Iterator的关系四、讨论一下集合遍历的常见问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复