一. 前言
在使用使用ArrayList的subList的时候,发生了ConcurrentModificationException的异常。接下来看看如何处理
二. 异常场景
2.1 代码如下
下面是产生异常的代码。
public static void main(String[] args) throws InterruptedException {
// 1 定义一个String的集合,并且添加3个元素 【"z","k","x"】
List<String> sourceList = new ArrayList<String>(2);
sourceList.add("z");
sourceList.add("k");
sourceList.add("x");
// 2 使用subList方法 表面上把集合的前2个元素截取出来放在新的集合newList中
// 表面上此时newList 应该是【"z","k"】
List<String> newList = sourceList.subList(0, 2);
// 3 往sourceList的0号位置 添加新的元素 sourceList应该变成了 【“cs”,"z","k",“x”】
sourceList.add(0,"cs");
// 4 意图在控制台上打印新的集合newList的0号位置 想观察一次此时newList的0号元素到底是什么
// 结果如果是 "z" 说明:【newList 是 sourceList的副本,此时两者已经没什么关系】
// 结果如果是 "cs" 说明:【newList 是 sourceList的视图,修改sourceList会影响到newList】
// 结果意想不到的是 这个地方抛出了ConcurrentModificationException 异常
System.out.println(newList.get(0));
}
2.2 异常截图
三. 原因分析
3.1 modCount变量
ArrayList 的父类AbstractList中定义了一个变量modCount,顾名思义这个变量就是用来记录ArrayList被修改的次数,modCount 初始值为0,ArrayList 源代码如下。
protected transient int modCount = 0;
3.2 modCount解析
ArrayList 中每每有增删改的变动,都会导致modCount加1,源代码如下。
// 例如 ArrayList 的 add 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 在这里modCount+1
elementData[size++] = e;
return true;
}
// 继续深入到 ensureCapacityInternal 方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity); // 在这里modCount+1 这里再继续深入
}
// 继续深入到ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3.3 SubList源码
ArrayList 使用SubList的时候并没有创建新的List,而是引用原来的List,并且把原本List的modCount复制了过来,源代码如下。
3.4 ConcurrentModificationException异常产生原因
subList在做增删改查时,都会对比一下自己的modCount 和 原生的list的modCount,如果对应不上就会抛出ConcurrentModificationException异常,源代码如下。
// subList的get 方法 调用之前要checkForComodification
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
// 跟入到checkForComodification
private void checkForComodification() {
// 比较subList 的modCount 和 源list的modCount 不相等等则抛出异常
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
四. 结论和解决方案
4.1 原因总结
至此,我们在上面代码中发生异常的原因已经一目了然了,再来看一下源代码:
4.2 解决方案
为了避免出现ConcurrentModificationException异常,我们在开发时要慎用subList,可以自行使用stream来截取需要的部分。
// List<String> newList = sourceList.subList(0, 2);
// 使用流的方式代替subList截取
List<String> newList = sourceList.stream().skip(0).limit(2).collect(Collectors.toList());
可以对subList 进行二次封装,封装成一个新的ArrayList。
List<String> newList = sourceList.subList(0, 2);
// 对subList的结果,再次封装成新的List
ArrayList newList1 = new ArrayList<>(newList);
五. 后话
其实上面的这个异常问题,在阿里巴巴的开发规范中早有说明,原文如下:
标签:modCount,ConcurrentModificationException,ArrayList,subList,sourceList,newList,解决 From: https://www.cnblogs.com/xsl-soul/p/16984492.html【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。