在java集合中调用remove方法后调用iterator.next()报错

  • 例如使用Arraylist集合返回的一个iterator迭代器,我们这样使用会报错

    • ArrayList<Integer> arrayList = new ArrayList<>();
      arrayList.add(1);
      Iterator<Integer> iterator = arrayList.iterator();//获得迭代器
      arrayList.add(2);
      if (iterator.hasNext()) System.out.println(iterator.next());
      
  • 分析

    • 我们来看**iterator.next()方法源码**:

      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();
      }
      
    • ArrayList 继承了 AbstractList 其中**AbstractList** 中有个**modCount** 代表了集合修改的次数。在**ArrayListiteratornext()方法中会判断** expectedModCount modCount是否相等,如果相等继续执行,不相等报错

    • 这个expectedModCount是迭代器这个类的一个属性,调用ArrayList自身的add和``remove等改变集合大小的方法都会导致modCount+1,但是不会修改expectedModCount`

    • 返回的迭代器是ArrayList的一个私有内部类(下文有这个内部类的源码),在调用集合的iterator()方法返回迭代器的时候底层是new一个新的迭代器返回,它在创建的时候会将当前modCount的值赋给expectedModCount,因此创建的时候两者相等

    • 因此我们在创建迭代器后,如果使用了ArrayList本身的add方法导致modCount+1后,此时modCountexpectedModCount不相等了,再调用迭代器的next()方法就会报错

      • 注意:这里不止调用add会报错,调用ArrayList本身的remove也会报错,关键在于这些方法修改了modCount但是没有修改expectedModCount

      • 如果我们是用迭代器的remove方法则不会报错,因为迭代器的remove方法在删除元素后会将新的modCount的值赋给expectedModCount

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
        
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;//这个cursor表示的是下一个需要遍历的元素,lastRet表示上一次遍历到的元素
                lastRet = -1;
                expectedModCount = modCount;//这里会保持两者是一致的
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        
        • 注意:我们在删除lastRet索引位置表示的上一个遍历的元素后,将lastRet的索引赋给cursor表示下一个需要遍历的索引位置
          • 这个时候可能会有疑惑:为什么这个位置元素被删除了下一次遍历还是这个位置
            • 这是因为我们remove删除上一个元素后,数组大小-1,即被删除元素后的元素都会往前移动,此时lastRet这个索引在新数组中指向的就是刚刚的下一个元素
        • 删除之后将lastRet置为-1,而remove方法开始就会判断lastRet是否小于0,防止连续两次调用remove方法删除元素
    • 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;
          }
      
          @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();
              }
          }
      
          @Override
          @SuppressWarnings("unchecked")
          public void forEachRemaining(Consumer<? super E> consumer) {
              Objects.requireNonNull(consumer);
              final int size = ArrayList.this.size;
              int i = cursor;
              if (i >= size) {
                  return;
              }
              final Object[] elementData = ArrayList.this.elementData;
              if (i >= elementData.length) {
                  throw new ConcurrentModificationException();
              }
              while (i != size && modCount == expectedModCount) {
                  consumer.accept((E) elementData[i++]);
              }
              // update once at end of iteration to reduce heap write traffic
              cursor = i;
              lastRet = i - 1;
              checkForComodification();
          }
      
          final void checkForComodification() {
              if (modCount != expectedModCount)
                  throw new ConcurrentModificationException();
          }
      }
      
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一酒。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值