问题还原
- List<String> list = new ArrayList<String>();
- list.add("1");
- list.add("2");
- for (String item : list) {
- if ("1".equals(item)) {
- list.remove(item);
- }
- }
输出如下:
- before: 1 2
- after : 2
似乎并没有问题, 但当把 "1" 改成 "2" 却输出如下结果:
- before: 2 2
- after : 2
得到的结果不是我们所想要的.
问题探究
首先进行反编译, 得到如下代码:
- List<String> list = new ArrayList();
- list.add("2");
- list.add("2");
- Iterator var2 = list.iterator();
- while(var2.hasNext()) {
- item = (String)var2.next();
- if ("2".equals(item)) {
- list.remove(item);
- }
- }
从反编译出来的代码不难看出, 实际上就是使用的 Iterator 迭代器进行操作.
先看一下 ArrayList 类中的 remove(Object o) 方法:
- public boolean remove(Object o) {
- if (o == null) {
- for (int index = 0; index <size; index++)
- if (elementData[index] == null) {
- fastRemove(index);
- return true;
- }
- } else {
- for (int index = 0; index < size; index++)
- if (o.equals(elementData[index])) {
- fastRemove(index);
- return true;
- }
- }
- return false;
- }
可以看到这个方法内移除元素的核心方法是
fastRemove(int index)
, 源码如下:
- private void fastRemove(int index) {
- modCount++;
- int numMoved = size - index - 1;
- if (numMoved> 0)
- System.arraycopy(elementData, index+1, elementData, index, numMoved);
- elementData[--size] = null; // clear to let GC do its work
- }
代码先放在这, 再看另一个方法 list.iterator(), 代码如下:
- public Iterator<E> iterator() {
- return new Itr();
- }
返回一个对象, 继续跟踪这个 Itr() 类:
- private class Itr implements Iterator<E> {
- int cursor; // index of next element to return
- int lastRet = -1; // index of last element returned; -1 if no such
- int expectedModCount = modCount;
- Itr() {}
- public boolean hasNext() {
- return cursor != size;
- }
- @SuppressWarnings("unchecked")
- 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];
- }
- 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();
- }
- }
- @Override
- @SuppressWarnings("unchecked")
- public void forEachRemaining(Consumer<? super E> consumer) {
- Objects.requireNonNull(consumer);
- final int size = ArrayList.this.size;
- int i = cursor;
- if (i>= size) {
- return;
- }
- final Object[] elementData = ArrayList.this.elementData;
- if (i>= elementData.length) {
- throw new ConcurrentModificationException();
- }
- while (i != size && modCount == expectedModCount) {
- consumer.accept((E) elementData[i++]);
- }
- // update once at end of iteration to reduce heap write traffic
- cursor = i;
- lastRet = i - 1;
- checkForComodification();
- }
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
- }
先注意 Itr 类的第四行
int expectedModCount = modCount
, 跟踪这个 modCount 变量, 位于 AbstractList.java 文件中, 变量的注释大概的意思就是: 在使用迭代器遍历的时候, 该变量用来检查列表中的元素是否发生结构性变化 (元素数量发生改变), 在迭代器处理过程中, 该值发生变化就会抛出异常, 所以可以了解到该值是用于迭代器在遍历时做线程安全检查的.
接着看 hasNext() 方法, 返回 cursor != size 的布尔值, cursor 是下一个元素的下标, size 是原 ArrayList 的长度.
在上面的例子中, 起始 ArrayList 的长度 size 为 2, 从下标 0 (cursor 为 0) 开始遍历, 符合 remove() 条件, 将原 ArrayList 中的第 0 个元素删除, 此时 size 变为 1(cursor 的值也变为 1), 此时, 遍历到下标为 1 的元素, 但 size 由于元素的删除由 2 变为 1,cursor != size 返回 false 即没有下一个元素, 也就是 ArrayList 内的最后一个元素没有遍历到, 也就造成了上面的非预期的结果.
然后再看 next() 方法, 在其第一行就有一个
checkForComodification()
函数, 也在这个类体中:
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
这个就是用来检查原来的 ArrayList 与循环体开始前构造的迭代器的长度不一致, 就会抛出
ConcurrentModificationException
异常.
所以在循环体之内不要直接对元素进行 remove /add 操作, 由而引发
ConcurrentModificationException
异常, 应该在循环体之外先转换为 Iterator 迭代器再对其进行操作
来源: http://www.bubuko.com/infodetail-2603394.html