暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

不要在 foreach 循环里 remove/add元素

杨遥 2020-05-29
483

作者 | 杨遥


经常会有对元素增删的场景,但是你真的用对了吗。阿里规范有一条不要在 foreach 循环里进行元素的 remove/add的规范,为什么会这样呢,我们源码分析一波:


场景一foreach删除


可以看到当条件为"1".equals(item)元素被正常删除,但是把删除的条件换成"2".equals(item)的时候,结果却是

Exception in thread "main" java.util.ConcurrentModificationException

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

    为什么会这样呢,我们看一下源码分析,在list进行循环的时候,会首先进入next方法,然后进入next的checkForComodification()方法,checkForComodification,主要是检查modCountexpectedModCount的值是否一致,不一致则抛出异常,这两个初始值都为list.size()也就是2,但是随着元素的修改和增加modCount会进行自增操作,接着到了满足条件移除元素,断点走到了remove(Object o)这个方法,然后走到 fastRemove(index),方法首先就是modCount++,然后进行System.arraycopy将list重新赋值。

        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();
      }


      public boolean remove(Object o) {
      if (o == null) {
      for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
      fastRemove(index);
      return true;
      }
      } else {
      for (int index = 0; index < size; index++)
      if (o.equals(elementData[index])) {
      fastRemove(index);
      return true;
      }
      }
      return false;
      }


      private void fastRemove(int index) {
      modCount++;
      int numMoved = size - index - 1;
      if (numMoved > 0)
      System.arraycopy(elementData, index+1, elementData, index,
      numMoved);
      elementData[--size] = null; clear to let GC do its work
      }

      list元素移除前后对比,原来有元素1和2,移除后2往前挪了一个位置,第二个位置赋值为null让GC回收

      原来的集合:

      移除后的集合:

      这下应该知道为什么条件为“1”的时候删除不报错,为“2”报错了吧,当条件是1的时候list移除了,集合的size已经变成了1了,压根就没进入第二次循环,所以当你在foreach调用删除元素的时候,可能你的集合根本没有遍历完所有的元素


      迭代器删除


          List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
        String item = iterator.next();
        if ("1".equals(item)) {
        iterator.remove();
        }
        }

        推荐使用迭代器删除, iterator.remove()删除的源码,可以看到每次remove元素之后,虽然modCount++了,但是可以在源码里面看到这么一句,expectedModCount = modCount;expectedModCount重新赋值了,下次再检查的时候,

        也就不会再抛出ConcurrentModificationException

            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();
          }
          }


          文章转载自杨遥,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论