• 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁


    下图是来自阿里巴巴java开发手册,里面的例子有些特殊,在后面会有一般的例子,请往后看。

    个人理解:在foreach循环里面进行元素的remove/add操作就是:在foreach循环里使用集合本身的remove/add方法, Iterator方式就是使用集合对应的iterator的remove/add方法,即下图中的iterator.remove()  //删除当前遍历的这个元素

     foreach本质上是java中的语法糖,对数组操作时,底层原理和普通的for循环一样;而对列表操作时,实际上是采用了迭代器的方法。

    来源于阿里巴巴java开发手册

    为什么不要使用集合本身的remove/add方法呢?

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    for (String temp : list) {
         if("1".equals(temp)){
             list.remove(temp);
       } 
    }

    反编译结果如下:

    List list = new ArrayList();
    list.add("1");
    list.add("2");
    Iterator i$ = list.iterator();     // 语句1
    do
    {
        if(!i$.hasNext())           
             break;
        String temp = (String)i$.next(); // 语句2 
      if("1".equals(temp)) list.remove(temp); // 语句3 
    }
    while(true);

    ArrayList的iterator()方法如下:

    重要参数:

    modCount:是ArrayList修改的次数,在add()、remove()/removeAll()、clear()等方法中会修改modCount的值。

    expectedModCount:在获得ArrayList对象的Iterator的时候,把modCount的初值赋给expectedModCount

    语句1: list.iterator() 返回的是一个Itr对象,

    语句2: next()方法中,首先会执行checkForComodification()方法(源码如下),如果 modCount != expectedModCount 就会抛出ConcurrentModificationException异常,在语句3中,执行了list.remove(temp),会修改modCout的值,那么在下一次执行next()方法的时候就会报错!! 所以不要在foreach循环里面进行元素的remove/add操作

    另:如果把上面代码  if("1".equals(temp)) 改成  if("2".equals(temp)) 即直接删除最后一个元素,会抛出ConcurrentModificationException异常,因为在删除最后一个元素的过程中,首先cursor 等于 size, 然后执行 a.remove(temp)  【-》modCount发生改变】方法后,size=size-1, 再执行hashNext()方法,发现返回结果是false(cursor != size) 又会进行一次循环,在next()方法里面就抛出异常。 所以不要在foreach循环里面进行元素的remove/add操作

    public Iterator<E> iterator() {
        return new Itr();
    }
    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;
        }
    
        @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];
        }
        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();
            }
        }final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

    为什么说上面的例子有些特殊,因为list集合中只有两个数据,如果是只删除第一个数据,执行list.remove(temp)后, cursor=size-1,  size=size-1, 所以就会推出循环。没有抛出异常。

    将例子改为:

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    for (String temp : list) {
         if("1".equals(temp)){
             list.remove(temp);
       } 
    }

    现在有了三个元素,除开直接删除倒数第二个元素不会抛出异常(因为会出现  cursor=size-1,  size=size-1的情况,然后退出循环),直接删除其他元素都会抛出ConcurrentModificationException异常!!

    如果是下面的写法,更是会抛出异常!

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    for (String temp : list) {
         list.remove(temp);
    }

    直接使用iterator的remove/add方法对集合做修改:

    List<String> list = new ArrayList<>();      
    list.add("1");      
    list.add("2");
    list.add("3");
    list.add("4");
    Iterator iterator = list.iterator();
    while(iterator.hasNext()) {
        String temp = (String) iterator.next();
        iterator.remove();
    }
  • 相关阅读:
    C# DataGridView导出到Excel
    面试时,怎样“谈薪”?
    7月11号 列表类型的内置方法及应用
    6月1号 字符编码
    文件相关知识
    Django框架之第二篇app注册、静态文件配置、form表单提交、pycharm链接数据库、Django使用mysql数据库、表字段的增删改查、表数据的增删改查
    函数相关知识
    Django框架之第三篇(路由层)有名/无名分组、反向解析、路由分发、名称空间、伪静态、图书管理系统表设计
    初识Django
    Django框架之第五篇(模型层)单表操作(增删改查)、单表查询和必知必会13条、单表查询之双下划线、Django ORM常用字段和字段参数和关系字段
  • 原文地址:https://www.cnblogs.com/DDiamondd/p/11307825.html
Copyright © 2020-2023  润新知