2014-07-21 51 views

回答

5

ConcurrentModificationException不会被Iterator.remove()抛出,因为这是允许的方式在迭代时修改集合。

如果您更改以任何其他方式进行迭代的集合,那么您可能会遇到异常。

如果在同一个集合中有两个迭代器,并且您通过其中一个迭代器删除,那么您也有可能会遇到异常。


iterator.remove从list.remove而list.remove并抛出迭代器不会抛出异常能做什么不同?

该协议是,当您通过迭代器移除时,迭代器的实现能够更新其数据结构以考虑删除。相比之下,如果通过集合对象删除(或插入或替换),则无法更新迭代器数据结构以使其与集合保持一致。

(还有,非并发集合类型不落实是线程安全的问题,所以你也可以有异常,如果集合和迭代器用于/由不同的线程更新。)

+0

我调试了迭代器实现的代码,并发现了这一点。感谢你的信息。 – javafan

+0

迭代器工作在副本如果集合,所以如果你先使用迭代器移除元素,它将它从集合中移除并更新它的缓存并排。 – Akash5288

+0

@ akash746 - 你有证据吗?这种方式的特定集合类型? –

11

我认为你的意思是,如果你正在迭代一个列表,为什么list.remove()会导致ConcurrentModificationException被抛出,而iterator.remove()不会呢?

考虑这个例子:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); 

    for (Iterator<String> iter = list.iterator(); iter.hasNext();) { 
     if (iter.next().equals("b")) { 
      // iter.remove(); // #1 
      // list.remove("b"); // #2 
     } 
    } 

如果您取消注释行#1,它会正常工作。如果您取消注释第2行(但留下#1注释),则会导致后续调用iter.next()丢弃ConcurrentModificationException

原因是迭代器是一个单独的对象,它具有对基础列表内部状态的一些引用。如果在迭代器运行时修改列表,可能会导致迭代器表现不佳,例如,通过跳过元素,重复元素,索引数组末尾,等等。它试图检测这种修改,所以它会抛出ConcurrentModificationException如果它。

删除通过迭代作品的元素,不会引起异常,因为这将更新基础列表这指的是内部的迭代器的状态,所以一切都可以保持一致。

然而,没有什么特别的iterator.remove(),使它在所有情况下工作。如果有多个迭代器在同一个列表上迭代,则由其中一个进行的修改将导致其他问题。考虑:

Iterator<String> i1 = list.iterator(); 
    Iterator<String> i2 = list.iterator(); 
    i1.remove(); 
    i2.remove(); 

我们现在有两个迭代器指向同一个列表。如果我们使用其中一个修改列表,它会中断第二个操作,因此调用i2.remove()将导致ConcurrentModificationException

+0

这其实是更正确的答案 – Sarief

1

因为它是抛出异常的迭代器。如果您致电List.remove()它不知道拆除,只是在其脚下发生了变化。如果您致电Iterator.remove(),它知道当前元素已被删除,应如何处理。

0

下面是一个例子,说明如果集合迭代器没有检查基础集合的修改,事情可能会出错。这是ArrayLists的迭代器是如何实现的:

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 

    public E next() { 
     checkForComodification(); 
     int i = cursor; 
     if (i >= size) throw new NoSuchElementException(); 
     // ... 
     cursor = i + 1; 
     return (E) elementData[lastRet = i]; 
    } 

    public void remove() { 
     // ... 
     ArrayList.this.remove(lastRet); 
     // ... 
     cursor = lastRet; 
     lastRet = -1; 
    } 

让我们来看一个例子:

List list = new ArrayList(Arrays.asList(1, 2, 3, 4)); 
Iterator it = list.iterator(); 
Integer item = it.next(); 

我们删除第一个元素

list.remove(0); 

如果我们想现在就打电话it.remove(),迭代器将删除号码2,因为这是lastRet指向的字段。

if (item == 1) { 
    it.remove(); // list contains 3, 4 
} 

这将是不正确的行为!迭代器的合约指出remove()删除next()返回的最后一个元素,但在并发修改的情况下无法保留它的契约。因此它选择安全并抛出异常。

其他馆藏的情况可能更加复杂。如果您修改HashMap,则可能会根据需要增加或减少。那时候,元素将落入不同的桶中,并且迭代器保持指向桶的指针,之后重新刷新将完全丢失。

请注意iterator.remove()本身并没有抛出异常,因为它能够更新这两个它自己的内部状态和集合。然而,在相同实例集合的两个迭代器上调用remove()会抛出,因为它会使其中一个迭代器处于不一致状态。

相关问题