下面的示例来至于阿里巴巴Java开发手册的集合处理部分的第7条:

运行如下代码,会发现正确运行。

  1. public static void hasNotExcption() {
  2. List<String> list1 = new ArrayList<String>();
  3. list1.add("1");
  4. list1.add("2");
  5.  
  6. for (String item : list1) {
  7. System.out.println("item : "+item);
  8. if ("1".equals(item)) {
  9. list1.remove(item);
  10. }
  11. }
  12.  
  13. }

但是运行如下代码,则异常:java.util.ConcurrentModificationException(和1中的代码区别是上面移除判断条件是1,下面的判断条件是2)

  1. public static void hasExcption() {
  2. List<String> list1 = new ArrayList<String>();
  3. list1.add("1");
  4. list1.add("2");
  5.  
  6. for (String item : list1) {
  7. System.out.println("item : "+item);
  8. if ("2".equals(item)) {
  9. list1.remove(item);
  10. }
  11. }
  12.  
  13. }

再看如下代码示例,运行结果见注释:

  1. /**
  2. *没有异常
  3. */
  4. public static void hasNotExcption() {
  5. List<String> list1 = new ArrayList<String>();
  6. list1.add("1");
  7. list1.add("2");
  8. list1.add("3");
  9. list1.add("4");
  10.  
  11. for (String item : list1) {
  12.  
  13. System.out.println("item : " + item);
  14. // 移除1和3时候会抛ConcurrentModificationException异常
  15. // 但是移除2的时候不会抛出异常
  16. if ("3".equals(item)) {
  17. list1.remove(item);
  18. }
  19. }
  20.  
  21. }
  22.  
  23. /**
  24. * 有异常
  25. */
  26. public static void hasExcption1() {
  27. List<String> list1 = new ArrayList<String>();
  28. list1.add("1");
  29. list1.add("2");
  30. list1.add("3");
  31.  
  32. for (String item : list1) {// 增强的for循环底层实现使用的是迭代器
  33. if ("1".equals(item)) {
  34. // 移除并修改“modCount变量”,导致下次遍历时候异常
  35. list1.remove(item);
  36. }
  37. }
  38.  
  39. }
  40.  
  41. /**
  42. * 有异常
  43. */
  44. public static void hasExcption2() {
  45. List<String> list1 = new ArrayList<String>();
  46. list1.add("1");
  47. list1.add("2");
  48. list1.add("3");
  49.  
  50. for (String item : list1) {
  51. if ("3".equals(item)) {
  52. list1.remove(item);
  53. }
  54. }
  55.  
  56. }

通过上面三个例子是不是发现了结论?只要移除的是倒数第二个元素的话,就不会发生异常!的确是,移除倒数第二个元素的话就不会异常,那究竟是为什么呢?

知识点1:关于增强for循环的实现

集合的增强for循环实现内部使用的是迭代器,可以通过eclipse的F5调试跟踪。

  1. List<String> list1 = new ArrayList<String>();
  2. list1.add("1");
  3. list1.add("2");
  4. list1.add("3");
  5. for (String item : list1) {
  6. if ("3".equals(item)) {
  7. list1.remove(item);
  8. }
  9. }

知识点2:集合如果判定的是并发修改错误?

细心的话可以通过异常发现异常的判断代码是:

  1. if (modCount != expectedModCount)
  2. throw new ConcurrentModificationException();

其中的modCount是记录集合的修改次数,而expectedModCount是记录的预期的修改次数,当集合的修改次数和预期修改次数不一致的时候则发生异常。

那么什么时候会修改modCount呢?当我们对集合进行删除或者添加元素时候此时记录集合的修改次数就会发生增加,但是期望的修改次数不会变化,当检查判断异常条件时候就会根据条件处理。

到此回归到上面问题,对于hasExcption1和hasExcption2是如何发生异常的?本例对hasExcption1进行详细解释:

  1. /**
  2. * 有异常
  3. */
  4. public static void hasExcption1() {
  5. List<String> list1 = new ArrayList<String>();
  6. list1.add("1");
  7. list1.add("2");
  8. list1.add("3");
  9. // 增强的for循环底层实现使用的是迭代器。所以每一次都是调用一个hasNext,然后在调用next方法返回元素给item
  10. for (String item : list1) {
  11.  
  12. if ("1".equals(item)) {
  13. // 移除并修改“modCount变量”,导致下次遍历时候异常
  14. list1.remove(item);
  15. }
  16. }
  17.  
  18. }

增强的for循环底层实现使用的是迭代器。所以每一次都是调用一个hasNext,然后在调用next方法返回元素给item。当我们删除元素之后,下一次循环时候会先调用hasNext是否还有元素,此时还有。在调用next方法返回该元素给item,但是调用next方法时候会检测

集合修改次数和预期修改次数是否相等,如果不等的话则抛出异常。(本例是不等的,因为移除1之后modCount=4,而expectModCount=3)。

那么为什么删除倒数第二个元素不会异常呢?示例代码:

  1. /**
  2. *没有异常
  3. */
  4. public static void hasNotExcption() {
  5. List<String> list1 = new ArrayList<String>();
  6. list1.add("1");
  7. list1.add("2");
  8. list1.add("3");
  9. list1.add("4");
  10.  
  11. for (String item : list1) {
  12. if ("3".equals(item)) {
  13. list1.remove(item);
  14. }
  15. }
  16.  
  17. }

因为我们删除倒数第二个元素时候,此时size=size-1,当删除完元素进入下一次循环时候,此时hasNext方法判断是否还有元素的时候返回是false(hasNext使用的是游标和size进行比较)。所以就不会调用next方法了。上面例子我们知道异常就是在next方法中抛的,所以

删除倒数第二个元素就不会有异常。

注意:如果删除的是倒数第二个元素那么最后的结果是否正确呢?答案:显然是不正确的。

通过如下示例代码检验:

  1. /**
  2. *没有异常
  3. */
  4. public static void hasNotExcption() {
  5. List<String> list1 = new ArrayList<String>();
  6. list1.add("1");
  7. list1.add("2");
  8. list1.add("3");
  9. list1.add("3");
  10.  
  11. for (String item : list1) {
  12. if ("3".equals(item)) {
  13. list1.remove(item);
  14. }
  15. }
  16. // 显然最后一个3没有删除,所以结果错误
  17. System.out.println(list1); //[1, 2, 3]
  18.  
  19. }

结果是错误的,原因:因为最后一个元素由于在移除倒数第二个元素时候将size-1了,所以遍历集合提前结束了。所以没有完全移除掉所有的“3”

Java中集合删除元素时候关于ConcurrentModificationException的迷惑点的更多相关文章

  1. java操作数组转list集合删除元素报错ConcurrentModificationException

    public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>( ...

  2. JAVA中循环删除list中元素的方法总结【转】

    印象中循环删除list中的元素使用for循环的方式是有问题的,但是可以使用增强的for循环,然后今天在使用时发现报错了,然后去科普了一下,再然后发现这是一个误区.下面就来讲一讲..伸手党可直接跳至文末 ...

  3. java中循环删除list中元素的方法

    重点哈 印象中循环删除list中的元素使用for循环的方式是有问题的,但是可以使用增强的for循环,然后今天在使用时发现报错了,然后去科普了一下,再然后发现这是一个误区.下面就来讲一讲..伸手党可直接 ...

  4. Java中集合的概述

    一.集合和数组的区别 1.数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 2.集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数 ...

  5. Java中集合List,Map和Set的区别

    Java中集合List,Map和Set的区别 1.List和Set的父接口是Collection,而Map不是 2.List中的元素是有序的,可以重复的 3.Map是Key-Value映射关系,且Ke ...

  6. JAVA中集合转数组遍历

    JAVA中集合的遍历的一种方法时集合转数组遍历,也是就调用Collection中的toArray(). 代码: public static void main(String[] args) {     ...

  7. Java中集合List,Map和Set的差别

    Java中集合List,Map和Set的差别 1.List和Set的父接口是Collection.而Map不是 2.List中的元素是有序的,能够反复的 3.Map是Key-Value映射关系,且Ke ...

  8. 遍历List集合删除元素的出现报错

    遍历List集合删除元素的出现报错   遍历List集合删除元素的时候会发生索引越界异常或内容遍历不全等问题. 例子: List<String> al = new ArrayList< ...

  9. js 遍历集合删除元素

    js 遍历集合删除元素 /** * 有效的方式 - 改变下标,控制遍历 */ for (var i = 0; i < arr.length; i++) { if (...) { arr.spli ...

随机推荐

  1. Java字符串和容器

    String Java.lang.String是Java的字符串类. Srting是一个不可变对象,所有对String修改的操作都需要构造新的String实例. String可以由char数组或字符串 ...

  2. SpringMVC Hello World

    前言 新年伊始,元宵佳节,窗外灯火通明,炮声连连.北漂以来第一次一个人在北京过十五. 切入正题,收假后一边要赶项目进度还要学习java,so在元宵佳节之际写了第一篇SpringMVC Hello Wo ...

  3. 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

    Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...

  4. Oracle11g em启动报此网站的安全证书有问题的解决方案

    http://blog.sina.com.cn/s/blog_a32eff280101cgje.html C:\>emctl status dbconsoleOracle Enterprise ...

  5. IEnumerable、ICollection、IList、List之间的区别与方法介绍

    区别 以下列出IEnumerable.ICollection.IList.List继承关系.(这里带有泛型,非泛型也是一样的关系) IEnumerable<T>: public inter ...

  6. jquery 截取屏幕

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  7. 【Java】模拟Sping,实现其IOC和AOP核心(二)

    接着上一篇,在上一篇完成了有关IOC的注解实现,这一篇用XML的方式实现IOC,并且完成AOP. 简易的IOC框图 注解的方式实现了左边的分支,那么就剩下右边的XML分支: XmlContext:这个 ...

  8. javascript基于对象的弹出框封装

    先睹为快,移动端:戳这里,打开页面后点击投票查看效果.PC端测试直接切换body的overflow属性:hidden和auto一样可以,比下面相对简化,又有人说这样偶尔不行..如果你知道优缺点欢迎给出 ...

  9. JavaScript 把字符串类型转换成日期类型

    今天在写习题时,遇到些小问题,在这里把答案分享给大家,希望能帮助到大家! 一.把字符串转换成日期类型 var str = "1997-3-12"; var d = new Date ...

  10. node通过QQ邮箱发送邮件

    在nodejs里面使用插件,不多说,首先下载: npm install emailjs 下载好之后,先别急着写代码,应该先设置一下,我这里用QQ邮箱举例子. 首先登陆QQ邮箱,然后点击:设置-> ...