为什么使用迭代器时可以用集合对象删除元素?

[图片] [图片] [图片] [图片]
关注者
89
被浏览
38,790
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

我觉得题主的首要问题是被自己写的代码(以及JDK的ArrayList那可恶的API)给坑了。

ArrayList上有两个版本的remove方法:

public E remove(int index)
public boolean remove(Object o)

题主很可能以为自己调用的是第二个版本,但实际上调用的是第一个版本——remove(3) 删除了位于末尾的元素,而不是位于倒数第二的元素。

ArrayList.iterator() 返回出来的 Iterator<E>,里面的hasNext()是不关心modification count的,而next()会去检查modification count:

    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];
        }

        // ...
    }

所以题主的那个程序实际做的事情就是:

  • 通过 ArrayList.iterator() 得到了一个新的iterator,开始遍历
  • list.remove(3):删除了位于index 3的元素(Integer.valueOf(4) 得到的对象)
  • 然后调用iterator.hasNext(),得到false,于是就退出了循环而没有去执行那个会检查modification count的next()方法。

ArrayList的JavaDoc说:

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

没有特别指定Iterator里的哪些方法一定会根据fail-fast原则而抛异常。但Iterator.hasNext()的JavaDoc说:

hasNext
boolean hasNext()
Returns true if the iteration has more elements. (In other words, returns true if next() would return an element rather than throwing an exception.)
Returns:true if the iteration has more elements

就这个规定来说,我觉得题主观察到的现象应该算是JDK实现的上的巧合:因为如果在这个位置调用next()的话会抛ConcurrentModificationException异常,所以hasNext()也要返回false,于是就好啦。

但JDK这个具体实现看起来还是有bug,应该让hasNext()也做checkForComodification()的不抛异常对应动作才对。

就这样。