为什么不能在foreach循环进行remove/add操作?

废话少说,直接上测试代码:

public class ForeachTest {
  public static void main(String[] args){
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("hello1");
    list.add("hello2");
//    for (int i = 0; i < list.size(); i++) {
    for(String s:list){
      list.remove("hello");
      System.out.println(s);
    }
  }
}

结果如下:

为什么不能在foreach循环进行remove/add操作?

然后换一种遍历方式:

public class ForeachTest {
  public static void main(String[] args){
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("hello1");
    list.add("hello2");
    for (int i = 0; i < list.size(); i++) {
//    for(String s:list){
      list.remove("hello");
      System.out.println(list.get(i));
    }
  }
}

结果如下:

为什么不能在foreach循环进行remove/add操作?


以上是两种测试的对比现象(结果),那么既然有了结果接下来就要分析原因了。

控制台抛异常首先定位异常抛出的地方,通过查看ArrayList源码可以得知异常触发点为:

为什么不能在foreach循环进行remove/add操作?

可知,异常的原因是因为modCount != expectedModCount导致的。

接下来就是分别查看这两个参数的含义和可以修改他们的操作了。

  • modCount:是AbstractList的一个成员变量:
    protected transient int modCount = 0;

    但是ArrayList是继承AbstractList的,这里modCount的修饰符是protected,所以是可以被subclass继承的。
    继续查看源码,对modCount的英文解释为:

    The number of times this list has been <i>structurally modified</i>.翻译:记录该list结构被修改的次数。
    
    查看源码中该变量的修改情况,发现就是在add/remove的时候,修改了modeCount的值。
  • expectedModCount:是ArrayList的一个内部类的成员变量
    为什么不能在foreach循环进行remove/add操作?
    说明:这里我们知道增强for底层的实现还是通过Iterator实现的,所以这里就不难理解为什么出现在这个地方了。

  • 在Iterator遍历开始就设置了两个参数相等,那么是什么情况导致了两个参数不等呢?
    前面我们知道在对集合进行add和remove的时候会修改modCount的值,而我们整个iterator都没有修改expectedModCount的值,所以在我们使用增强for遍历的时候,如果进行add/remove的操作,就会导致两个参数不等,然后抛出异常。

fail-fast机制:以上我们是从代码层面分析出了,为什么抛出异常,但是我们其实还不是很清楚为什么要这么设计。其实这是Java的fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。

详细解释如下:

为什么不能在foreach循环进行remove/add操作?


上面我们基本了解了异常原因和原理,接下来的问题就是我们该如何在业务中处理这种情况呢?

  1. 第一种方式:我们使用一般的for循环即可;
  2. 第二种方式:我们使用Iterator自带的remove方法进行操作,因为自带的方法是同步了modCount和expecteModCount的;
  3. 第三种方式:使用Java8中的filter;
  4. 第四种方式:新建一个集合来装过滤后的数据(这种方式比较消耗内存,不推荐使用);
  5. 第N种方式:已经知道原因和原理了,其实处理方式就很灵活了,可以自己想想其他方式了,哈哈!!!