背景
如下代码报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("b")) { list.add("d"); } } System.out.println(list); } }
|
可以看到抛出的异常名为: ConcurrentModificationException
。
为什么集合删除元素不能用 for-each/Iterator?
源码分析
部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public interface List<E> { Iterator<E> iterator(); boolean add(E e); }
public class ArrayList<E> extends AbstractList<E> implements List<E> { public Iterator<E> iterator() { return new Itr(); } public boolean add(E e) { modCount++; add(e, elementData, size); return true; } private class Itr implements Iterator<E> {
int expectedModCount = modCount;
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]; }
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { protected transient int modCount = 0; }
|
分析
我们报错的地方在 String s = it.next();
这里,所以我们要看看这里面发生了什么,也就是程序怎么走的:
- 首先使用了
iterator()
方法
- 这个
iterator()
方法返回一个new Itr()
- 进入到
Itr
这个类里面
Itr
中有一个 next()
方法,而这个也就是我程序里调用的 next()
- 这个
next()
方法里面有一个 checkForComodification()
方法
- 进入到
checkForComodification()
这个方法里面,发现了一个条件语句,里面抛出 ConcurrentModificationException
异常,这里应该就是 “病根” 了!条件语句是 if (modCount != expectedModCount)
- 找到
expectedModCount
这个变量,这个变量最开始是和 modCount
相等的,所以是什么原因导致了这俩变量不相等了,继续找 modCount
- Alt-Enter 进入
modCount
,我们在 ArrayList
的父类 AbstractList
里面找到了该变量
- 当执行
ArrayList
里面的 add()
方法时,也就是 list.add("d");
这一操作,使得 modCount++
- 最终导致了
modCount != expectedModCount
解决
不要去使用 Iterator
,可以使用 for
,或是去使用 ListIterator
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); for (int i = 0; i < list.size(); i++) { String s = list.get(i); if (s.equals("b")) { list.add("d"); } } System.out.println(list); } }
public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); ListIterator<String> listIt = list.listIterator(); while (listIt.hasNext()) { String s = listIt.next(); if (s.equals("b")) { listIt.add("d"); } } System.out.println(list); } }
|
为什么 get()
就可以呢?看看 ArrayList
里面的 get()
的源码:
1 2 3 4
| public E get(int index) { Objects.checkIndex(index, size); return elementData(index); }
|
可以看到它并没有设置判断,可是为什么 ListIterator
就可以呢?
同样按照上面的源码分析过程看看它的 ListItr
类里面的 add()
方法,可以知道 ListItr
类的 add()
方法内,会重新将 “预期修改集合的次数” 赋值为 “修改集合的次数”,所以不会抛出并发修改异常。
总结
- 每次使用
add()
方法时,“修改集合的次数” 就会 +1
- “预期修改集合的次数” 初值等于 “修改集合的次数”
- 在使用
Iterator
进行遍历时,每次执行 next()
方法,都会判断一下 “修改集合的次数” 是否等于 “预期修改集合的次数”,如果不等,则抛出并发修改异常
- 也就是说,在遍历期间执行
add()
会导致 “修改集合的次数” != “预期修改集合的次数”,进而导致抛出并发修改异常,即,ConcurrentModificationException
异常
- 可以使用
ArrayList
里面的 get()
方法解决这样的问题
- 也可以使用
ListItr
类的 add()
方法来解决
- 为避免混淆,再此说明下:
Iterator
接口里面没有 add()
方法,在添加时使用的是 ArrayList
里面的 add()
方法;而 ListIterator
接口里面是有 add()
方法的,所以添加时使用的是 ListItr
类的 add()
方法