java为什么使用iterator进行删除元素其他list不行(底层源码解析)?

项目场景:

iterator下 hasnext() 死循环

循环删除数据异常记录


问题描述

通过循环删除集合中的某条数据时,采用iterator进行删除,但一不小心就会造成死循环,在这记录一下。


以下是错误代码


 Iterator<ReadXmlDDVO> iterator = selectDataList.iterator();
 while (iterator.hasNext()) {
       for (ReadXmlDDVO vo : matchSuccessList) {
           if (vo.getInvoiceId().equals(iterator.next().getInvoiceId()) && StringUtil.isNotBlank(vo.getMatchCode())) {
                iterator.remove();
           }
       }
 }
 

以下是正确代码


 Iterator<ReadXmlDDVO> iterator = selectDataList.iterator();
 while (iterator.hasNext()) {
    iterator.next();
       for (ReadXmlDDVO vo : matchSuccessList) {
           if (vo.getInvoiceId().equals(iterator.next().getInvoiceId()) && StringUtil.isNotBlank(vo.getMatchCode())) {
                iterator.remove();
           }
       }
 }


原因分析:

     while里没做处理,必须要做iterator.next(); ,这样iterator才能移动,没有移动next永远在第一个,形成了死循环,永远都出不来!


解决方案:

     while循环里面添加 iterator.next();

问题延申:

   为什么集合里面的删除一定要通过Iterator进行删除呢?

       当使用foreach遍历集合时对集合元素进行add / remove时,就会抛出ConcurrentModificationException。

for(String str : list){
    if(str.equals("haha")){
        list.remove(str);   //抛出异常
    }
}

// 异常
Exception in thread "main" java.util.ConcurrentModificationException

       在对错误分析之前,我们先对foreach进行解语法糖,进行反编译,可以得到以下代码:

Iterator iterator = userNames.iterator();
do
{
    if(!iterator.hasNext())
        break;
    String userName = (String)iterator.next();
    if(userName.equals("Jobs"))
        userNames.remove(userName);
} while(true);

       可以看到,foreach底层实际上是利用iterator和while循环实现的。以ArrayList为例,在调用iterator的时候,会直接返回一个Itr对象,那么我们看一下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;

       这是Itr对象的几个类成员变量,其中我们看到了一个叫作expectedModCount的字段,那么他是干什么用的呢?我们看下remove函数

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

modCount是ArrayList中的一个成员变量,它表示集合实际被修改的次数,当ArrayList被创建时就存在了,初始值为0。

expectedModCount 是iterator中的一个成员变量,而iterator是ArrayList的一个内部类,当ArrayList调用iterator()方法获取一个迭代器时,会创建一个iterator,并且将expectedModCount 初始化为modCount的值。只有该迭代器修改了集合,expectedModCount 才会修改。

       如源代码所示,我们可以看到在Itr进行remove时首先是检查lastRet,这个很合理,就是检查是否越界到最后一个元素。然后进行了checkForComodification检查,具体的操作如上面的函数所示,也就是检查了下modCount是否与expectedModCount是否相等,如果相等,就没事,如果不相等就标出我们上面所出现的异常,那modCount如何变化的呢,我们往下看

public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        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        return oldValue;    }

       上面是ArrayList的remove函数源码,函数中在每次执行remove时,都会对modCount加一,不仅仅只是在remove时加一,其实add(),clear()函数也会对modCount进行加一操作,那么modCount起什么作用呢,其实他就相当于一个记录ArrayList版本的变量,每对他进行操作时就会将其加一,表示进行了新的操作。

       ok,分析了这么多,我们回到最初的问题,为什么用list直接删除元素会报错,而要使用iterator进行删除。
       通过源码可以看出,在获取迭代器时,迭代器内的expectedModCount被初始化为modCount,此时如果直接用ArrayList对象直接remove,那么就会改变modCount的值(进行了加一),当迭代器迭代过程中进行checkForComodification检查时,就会发现expectedModCount != modCount,也就是发现当前版本和迭代器记录的版本不一样,那么迭代过程中肯定就会有问题,这时,就会报出之前的异常。
       那么,我们再来看下为什么用Itr删除时就可以安全的删除,不会报错呢?在他的remove函数中可以看到下面的一句话,首先其实还是调用了ArrayList的remove函数


       在调用完remove函数之后,modCount进行了赋值操作,相当于将最新的版本号告诉了迭代器,所以迭代器在进行异常检查的时候就不会报错,因为他俩是相等的。
       还有值得注意的一点是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Iterator 是用于遍历集合类(如 List、Set、Map 等)中元素的接口。它提供了一种统一的方式来访问集合中的每个元素,而不需要了解底层数据结构的细节。 使用 Iterator 的一般步骤如下: 1. 使用集合类的 iterator() 方法获取 Iterator 对象。 2. 使用 hasNext() 方法检查是否还有下一个元素。 3. 使用 next() 方法获取下一个元素。 4. 可选地使用 remove() 方法删除当前元素。 Iterator 的特点包括: 1. 只能向前遍历:Iterator 接口定义了 hasNext() 方法来判断是否还有下一个元素,以及 next() 方法来获取下一个元素。它不支持逆向遍历或随机访问。 2. 快速失败:如果在使用 Iterator 迭代过程中,集合发生结构性修改(例如添加或删除元素),则会抛出 ConcurrentModificationException 异常,以保证遍历的安全性。 3. 只读:Iterator 接口的 remove() 方法可以用于删除当前迭代器指向的元素,但不能用于修改集合中的元素本身。 下面是一个使用 Iterator 遍历 List 的示例代码: ```java List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element); } ``` 这段代码会依次输出 "A"、"B"、"C"。注意,在遍历过程中不要直接使用集合的 remove() 方法删除元素,而是使用 Iterator 的 remove() 方法来删除当前迭代器指向的元素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值