唠嗑部分
在切入正题之前我们先聊一聊java语言中list集合的几种语法层面的遍历方式
第一种:传统for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
第二种:增强for循环
for (String value : list) {
System.out.println(value);
}
第三种:forEach
list.forEach(System.out::println);
第四种:stream流
list.stream().forEach(System.out::println);
还有一种,iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
其实啊,第三种、第四种在遍历集合时,也会使用iterator迭代器去处理,但是,iterator这种方式如果使用不当,就可能会导致并发修改异常,那么今天小白就来说一说日常开发中如何去避免这个问题
并发修改异常长这个样子哈,以后见到了可别说不认识
ConcurrentModificationException |
---|
言归正传
案发现场:请看源代码
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
list.remove(value);
}
}
以上这个代码,就出现了我们说的并发修改异常
如图所示哈,从控制台我们可以看出,罪魁祸首在程序的22行,那么为什么会抛出这个异常呢?我们来一起探究一下
首先我们找到Itr类,它是匿名在ArrayList中的一个类,实现了Iterator接口,list.iterator()返回的就是这个类的实例
请看上图,类很简单,构造器没有做任何事情,三个成员变量
cursor:返回下一个元素的索引,int初始化为0
lastRet:返回最后一个元素的索引,没有则返回-1
看849行,modCount是ArrayList中的一个成员变量,表示对集合的修改次数,将其赋给expectedModCount变量
那么看重点,在我们的遍历代码中,似乎只有两个方法,我们分别贴一下吧
hasNext()
:size是集合中元素的数量,cursor初始为0
public boolean hasNext() {
return cursor != size;
}
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;
return (E) elementData[lastRet = i];
}
看上面这个方法,在获取元素的最前面,有个checkForComodification(),那这个是做什么的呢?
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
总算是看到ConcurrentModificationException了,原来是在这里抛出的,这也就与上面22行抛出异常吻合了,就在next()这个的方法
它会去判断一下集合的修改次数是否与expectedModCount(期望的修改次数)一致,如果不一致,就抛出异常
expectedModCount变量是跌代器的实例变量,出现不一致,难不成是modCount变了?
那是不是我们可以这样理解,在迭代过程中,modCount不能变,或者是始终让expectedModCount与modCount一致
上面使用list.remove()方法,是不是会修改modCount的?是的
看上图,remove一次,modCount会++,那么下次next()的时候,expectedModCount与modCount就不一致了
不只是remove,add也会,请自行研究
那么问题找到了,要如何解决呢,按照我们上述分析的思路,有三种解决办法?
1、modCount不能变
这个简单,在遍历过程中,不对集合中的元素操作就完了
2、modCount++之后,不让进行下次的next()
案例代码改造:手动break
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
list.remove(value);
break;
}
}
System.out.println(list);
3、始终让expectedModCount与modCount一致
我们对集合中的元素进行了操作,modCount肯定会变,expectedModCount这个变量也没有暴露方法,不能直接修改,其实啊Iterator也有一个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();
}
}
其他的就不多说了,看倒数第5行,问题就解决了,删除元素之后,手动的让expectedModCount = modCount
那么源代码修改为
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
iterator.remove();
}
}
System.out.println(list);
结语
1、集合有很多种遍历方式,在使用跌代器遍历时,删除元素不能使用list提供的remove,iterator.next()首先会检查expectedModCount与modCount是否相等,不相等会抛出并发修改异常。
2、使用Iterator提供的remove方法删除元素,底层会手动的让expectedModCount与modCount相等。
3、forEach、stream.forEach()在遍历集合时底层也会翻译为迭代器,删除元素也会抛出并发修改异常。
4、制作不易,一键三连再走吧,您的支持永远是我最大的动力!