开启掘金成长之旅!这是我参与「掘金日新计划 · 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
,为什么呢?
原因分析
让我们用一种可视化的方法来理解这是如何工作的。假设我们有一个如下的数组列表:
我们从索引3到索引6创建一个子列表,如下所示:
在这里,出于节省内存方面的考虑,子列表其实并没有拷贝一份数据,而只是原始列表的一个子视图,存储了原始列表开始的offset
。
因此,当开发人员使用subList访问任何数据时,subList使用这个偏移量和索引来计算位置,并在给定位置从原始列表中获取数据。
现在,为了确保subList正在访问正确的数据,它需要确保原始的List没有被修改。
为了实现这一点,subList检查原始List是否已被修改。如果原始List已经更新,而subList不知道这些更改,那么对subList执行的任何操作(add/remove/size)都会导致ConcurrentModificationException
。
注意,当您直接从subList中添加/删除数据时,它本质上是向原始List中添加/删除数据。但是,在这种情况下,subListt会知道原始List所做的更改。因此,在这种情况下,它不会抛出异常。
如何解决
- 如果您只需要子列表来读取/访问列表的某些部分,那么创建一个新的列表并将元素复制到新的list中。
- 如果您需要通过subList添加/删除项,那么确保每个ArrayList只对一个subList进行修改。如果对多个子列表进行修改,也会导致异常, 这种情况也可以用LinkedList代替ArrayList。