关于ArrayList的subList方法抛出ConcurrentModificationException

57 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

前言

最近在开发的过程中遇到了一个问题,比如下面的一段代码,大家觉得输出的结果是什么?

import java.util.*;  
  
publc class SubListTest {  
public static void main(String[] args) {  
    ArrayList<Integer> mainList = new ArrayList<>();  
    mainList.add(1);  
    mainList.add(2);  
    mainList.add(3);  
    mainList.add(4);  
  
    List<Integer> subList = mainList.subList(0, 2);  
  
    mainList.add(5);  
  
    System.out.println("SubList size: " + subList.size()); //????
    }  
}

你可能会认为是2,大错特错,结果会跑错,抛了ConcurrentModificationException,为什么呢?

原因分析

让我们用一种可视化的方法来理解这是如何工作的。假设我们有一个如下的数组列表:

image.png

我们从索引3到索引6创建一个子列表,如下所示:

image.png

在这里,出于节省内存方面的考虑,子列表其实并没有拷贝一份数据,而只是原始列表的一个子视图,存储了原始列表开始的offset

image.png

因此,当开发人员使用subList访问任何数据时,subList使用这个偏移量和索引来计算位置,并在给定位置从原始列表中获取数据。

image.png

现在,为了确保subList正在访问正确的数据,它需要确保原始的List没有被修改。 为了实现这一点,subList检查原始List是否已被修改。如果原始List已经更新,而subList不知道这些更改,那么对subList执行的任何操作(add/remove/size)都会导致ConcurrentModificationException

注意,当您直接从subList中添加/删除数据时,它本质上是向原始List中添加/删除数据。但是,在这种情况下,subListt会知道原始List所做的更改。因此,在这种情况下,它不会抛出异常。

如何解决

  1. 如果您只需要子列表来读取/访问列表的某些部分,那么创建一个新的列表并将元素复制到新的list中。
  2. 如果您需要通过subList添加/删除项,那么确保每个ArrayList只对一个subList进行修改。如果对多个子列表进行修改,也会导致异常, 这种情况也可以用LinkedList代替ArrayList。