阿里java开发规范,为什么不要在 foreach 循环里进行元素的 remove/add 操作,你真的知道为什么吗

阿里java开发规范,为什么不要在 foreach 循环里进行元素的 remove/add 操作?

官方案例

阿里只提出了要求以及案例,但并没有给予解释,不知道大家是否有运行一下下面的案例代码呢?

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class Test6 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
//        Iterator<String> iterator = list.iterator();
//        while (iterator.hasNext()) {
//            String item = iterator.next();
//            if ("2".equals(item)) {
//                iterator.remove();
//            }
//        }
        for (String item : list) {
            if ("2".equals(item)) {
                list.remove(item);
            }
        }
        System.out.println(list);
    }
}

ConcurrentModificationException异常分析

运行后,会出现ConcurrentModificationException,即并发修改异常

D:\JDK11\bin\java.exe "-javaagent:D:\IntelliJ IDEA 2021.3.1\lib\idea_rt.jar=57539:D:\IntelliJ IDEA 2021.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Idea
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
	at Test6.main(Test6.java:23)

可得两个主要报错

at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)

由报错信息可得ArrayList类的997行报错,进入可得:

 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];
        }

可见,是iterator进行next迭代时checkForComodification()方法进行了异常抛出

checkForComodification()

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

要知道具体如何抛出的异常,还得从增强for循环开始分析

增强for是iterator实现的

通过debug,可以看到增强for循环是用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;

        // prevent creating a synthetic constructor
        Itr() {}

        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];
        }

ConcurrentModificationException异常是在迭代时抛出的

上面来自997行的报错,at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997),就是来自这里的ite中的next()方法

而另一个报错,java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043),来自next()方法中的checkForComodification()方法

checkForComodification()

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
      }

这个方法很简单,就是比较modCountexpectedModCount,不一样,则抛出异常

modCount和expectedModCount是什么?

  • modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数,初始值为0。
  • expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。

ArrayList的remove方法怎么执行的?

remove()

public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

重点看fastRemove(es, i)方法

fastRemove(Object[] es, int i)

    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

重点来了fastRemove()中对modCount进行了+1操作,但是expectedModCount并没有得到更新,两者值出现了不同,因此抛出异常!

itearator是什么时候进行的checkForComodification()方法,为什么案例中删除1不会出现异常?

回答这个问题,必须清楚iterator迭代器迭代时的具体逻辑

  1. 初始化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;
    

    在foreach初始化时,定义了一个游标cursor,从0计数,每次迭代时cursor+1,同时初始化了expectedModCount,其值与modcount同步

  2. 在迭代之前,会执行hasNext()方法,判断是否要进行迭代

     public boolean hasNext() {
                return cursor != size;
            }
    

    如果下标与集合size相等,则不再进行next(),直接退出迭代,反之,进行next(),这就是iterator迭代器退出的核心机制,也是checkForComodification()是否会执行的重点,下面会详细说明

    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; //迭代时游标+1
                return (E) elementData[lastRet = i];
            }
    
  3. 回归正题,案例中,如果删除"1",并不会抛出异常

    原因分析:

    第一次进入迭代器,进行初始化后

    先进行hasnext(),此时游标cursor为0,size为2

    然后进行next(),cursor+1,为1

    当remove元素1时,size-1

    fastRemove():

     private void fastRemove(Object[] es, int i) {
            modCount++;
            final int newSize;
            if ((newSize = size - 1) > i)
                System.arraycopy(es, i + 1, es, i, newSize - i);
            es[size = newSize] = null;
        }
    

    进入第二次迭代时,这次不用再初始化

    依然是进行hasnext(),此时!cursorsize都为1,直接返回false,退出迭代!也就是说,还没到抛出异常,这迭代就已经退出了,因此不会抛出异常

  4. 至于为什么删除元素"2"会出现异常,想必不用多解释了

对比之下,Iterator的remove方法是如何实现的?

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

第10行,对expectedModCountmodCount进行了同步,因此不会出现并发修改异常

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Acerola-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值